-
Notifications
You must be signed in to change notification settings - Fork 0
/
city_result.py
136 lines (107 loc) · 4.4 KB
/
city_result.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
from common import UnsupportedCityException, MissingConfigVarException, configvar, locs
import functools
import pandas as pd
class Imputed(float):
def __repr__(self):
return f'{self.__class__.__name__}({float(self)})'
class Proxy(float):
def __repr__(self):
return f'{self.__class__.__name__}({float(self)})'
@configvar
def proxies():
"""
Allows using the value of one location as a proxy for another.
For example, the config
climate_change:
squamish: seattle
means that the location "squamish" will borrow its "Climate Change" factor value from the location "seattle" (climate_change.py currently only works for US locations).
"""
@functools.total_ordering
class CityResult:
UNSUPPORTED = 'UNSUPPORTED'
def __init__(self, loc, value_modules):
self.loc = loc
self.value_modules = value_modules
self.annual_values = {}
self.total_annual_value = 0
def _get_proxy_value(self, module):
try:
proxy_loc_name = proxies()[module.__name__][self.loc.name]
except (MissingConfigVarException, KeyError):
return None
proxy_loc = locs.__dict__[proxy_loc_name]
return Proxy(module.annual_value(proxy_loc))
def compute(self):
annual_values = {}
for module in self.value_modules:
annual_value = self._get_proxy_value(module)
if annual_value is None:
try:
annual_value = module.annual_value(self.loc)
except UnsupportedCityException:
annual_value = self.UNSUPPORTED
annual_values.setdefault(module.FACTOR_NAME, []).append(annual_value)
for k in annual_values:
if len(annual_values[k]) == 1:
# If it's a Proxy preserve it
annual_value = annual_values[k][0]
else:
annual_values_k = [
v for v in annual_values[k] if v is not self.UNSUPPORTED
]
if annual_values_k:
annual_value = sum(annual_values_k) / len(annual_values_k)
else:
annual_value = self.UNSUPPORTED
self.annual_values[k] = annual_value
if annual_value is not self.UNSUPPORTED:
self.total_annual_value += annual_value
def print(self):
print(self.loc.name.upper())
for value_name, annual_value in self.annual_values.items():
print(f'{value_name + ":":16} ', end='')
if annual_value == self.UNSUPPORTED:
print('UNSUPPORTED (total will be inaccurate)')
continue
print(f'{annual_value:8,.0f}', end='')
if isinstance(annual_value, Imputed):
print(' (IMPUTED)')
elif isinstance(annual_value, Proxy):
print(' (PROXY)')
else:
print()
print(f'{"Total:":16} {self.total_annual_value:8,.0f}')
print()
def __le__(self, other):
return self.total_annual_value < other.total_annual_value
def __eq__(self, other):
return self.total_annual_value == other.total_annual_value
def to_pandas(self):
d = self.annual_values.copy()
d['Total'] = self.total_annual_value
for key in d:
if d[key] is self.UNSUPPORTED:
d[key] = float('nan')
return pd.DataFrame(d, index=[self.loc.name.upper()])
# TODO maybe it's better to do this using Pandas?
def impute_missing_values_with_mean(city_results):
sums = {}
counts = {}
for city_result in city_results:
for key, value in city_result.annual_values.items():
if value is CityResult.UNSUPPORTED:
continue
if isinstance(value, Imputed):
raise ValueError('Should not re-impute on already imputed results')
sums.setdefault(key, 0)
counts.setdefault(key, 0)
sums[key] += value
counts[key] += 1
for city_result in city_results:
for key, value in city_result.annual_values.items():
if value is CityResult.UNSUPPORTED:
mean = sums[key] / counts[key]
city_result.annual_values[key] = Imputed(mean)
city_result.total_annual_value += mean
def to_pandas(city_results: list[CityResult]):
return pd.concat(r.to_pandas() for r in city_results)