This repository has been archived by the owner on May 19, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathchoropleth.py
233 lines (195 loc) · 7.17 KB
/
choropleth.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
'''
Contiene funciones para generar mapas coropléticos que muestran
conteos de ingresos agrupados por primera letra de CIE,
municipio del AMM y semana epidemiológica.
AMM = Área Metropolitana de Monterrey
'''
import itertools
import plotly
import plotly.graph_objects as go
import pandas as pd
import epiweeks
from filter_geojson import read_amm_geojson
from readers import read_entries, read_amm_municipalities
def group_dates_by_epiweeks(entries_dates: pd.Series) -> pd.Series:
'''
Agrupa fechas de ingreso for semanas epidemiológicas.
:param entries: Columna de fechas de ingreso.
:returns: `pandas.Series` de fechas de ingreso ya agrupadas.
'''
# años mínimo y máximo que aparecen en el archivo
minyear, maxyear = min(entries_dates).year, max(entries_dates).year
# unir semanas de los años que aparecen en el archivo
# y uno posterior
weeks = list(itertools.chain.from_iterable(
epiweeks.Year(year).iterweeks()
for year in range(minyear, maxyear + 2)
))
# lista de ingresos en semana epidemiológica
epidates = list()
# índice para iterar las semanas
i = 0
week = weeks[i]
for dfdate in entries_dates.tolist():
while True:
# si está dentro de la semana actual
if week.startdate() <= dfdate <= week.enddate():
epidates.append(week)
# siguiente ingreso
break
# si no, siguiente semana
# pero mismo ingreso
i += 1
week = weeks[i]
return epidates
def count_grouped_entries(entries: pd.DataFrame) -> pd.DataFrame:
'''
Cuenta los casos de ingresos agrupados por CIE, municipio y semana epidemiológica.
:param entries: `DataFrame` filtrado por CIE, debe contener las columnas:
[MUNIC, INGRE]
:returns: `entries` con una columna `CONT`, que cuenta de los casos agrupados.
'''
# agregar columna de conteos de casos
entries['CONT'] = 0
# contar casos
return (entries
.groupby(['MUNIC', 'INGRE'])
.count()
.reset_index())
def get_amm_entries(year: int, cie: str) -> pd.DataFrame:
'''
Filtra solamente los ingresos del AMM, agrupa las fechas
por semana epidemiológica y cuenta los casos de CIE.
:param year: Año del archivo a leer (EGRESO_`year`.csv).
:param cie: Primera letra de CIE.
:returns: Registros de ingresos del AMM, y con columnas extras:
[NOM_MUN, LETRA_CIE, CONT]
'''
entries = read_entries(year)
amm_munics = read_amm_municipalities()
# filtrar registros por
entries_amm = entries[
# Nuevo León
(entries['ENTIDAD'] == '19') &
# municipios del AMM
entries['MUNIC'].isin(amm_munics['MUNIC']) &
# primera letra de CIE coincide con el parámetro
entries['DIAG_INI'].str.startswith(cie)
# las columnas del estado y CIE ya no se usarán
].drop(columns=['ENTIDAD', 'DIAG_INI'])
# convertir columna de ingresos a tipo datetime
entries_amm['INGRE'] = pd.to_datetime(entries_amm['INGRE'])
# ordenar ascendente por ingresos
entries_amm.sort_values('INGRE', inplace=True)
# agrupar fechas de ingreso por semanas epidemiológicas
entries_amm['INGRE'] = group_dates_by_epiweeks(entries_amm['INGRE'])
entries_amm = count_grouped_entries(entries_amm)
# agregar columna con nombres de municipios
entries_amm = amm_munics.merge(entries_amm, on='MUNIC')
return entries_amm
def plot_entries_choropleth(year: int, cie: str, output: str = '') -> None:
'''
Genera un mapa coroplético animado sobre el conteo de
ingresos por municipio, CIE y semana epidemiológica.
:param year: Año del archivo a leer (EGRESO_`year`.csv).
:param cie: Primera letra de CIE.
:param output: Ruta relativa del archivo HTML para guardar el mapa coroplético.
'''
# si no se especificó nombre de archivo, generar uno
filepath = output if output else f'ingresos_{cie}_{year}.html'
print(f'{filepath}: Preparando datos...', flush=True)
entries_amm = get_amm_entries(year, cie)
# límites de casos por archivo (año)
mincount, maxcount = min(entries_amm['CONT']), max(entries_amm['CONT'])
munics_geojson = read_amm_geojson()
# por cada letra inicial de los CIE
# for cie_letter in entries_amm['LETRA_CIE'].unique():
# filtrar ingresos por la letra CIE
entries_amm = entries_amm.sort_values('INGRE')
# listas para animación del mapa
frames, steps = list(), list()
print(f'{filepath}: Generando mapa coroplético...', flush=True)
# por cada semana en los ingresos del CIE actual
for week in entries_amm['INGRE'].unique():
# filtrar ingresos por la semana
entries_ingre = entries_amm[
entries_amm['INGRE'] == week
].sort_values('MUNIC')
label = f'{week.year}, semana {week.week}'
name = f'frame_{week}'
frames.append({
'name': name,
'data': [
# mapa coroplético
dict(
type='choroplethmapbox',
geojson=munics_geojson,
locations=entries_ingre['MUNIC'],
z=entries_ingre['CONT'],
zmin=mincount,
zmax=maxcount,
hoverinfo='z+text+name',
text=entries_ingre['NOM_MUN'],
name='Casos en',
colorscale='Viridis',
marker_opacity=0.5,
marker_line_width=0,
)
]
})
steps.append({
'label': label,
'method': 'animate',
'args': [
[name],
{
'mode': 'immediate',
'frame': {
'duration': 500,
'redraw': True
},
'transition': {'duration': 300}
}
]
})
sliders = [{
'transition': {'duration': 300},
'x': 0.08,
'len': 0.88,
'currentvalue': {'xanchor': 'center'},
'steps': steps
}]
playbtn = [{
'type': 'buttons',
'showactive': True,
'x': 0.045, 'y': -0.08,
'buttons': [{
'label': 'Play',
'method': 'animate',
'args': [
None,
{
'mode': 'immediate',
'frame': {
'duration': 500,
'redraw': True
},
'transition': {'duration': 300},
'fromcurrent': True
}
]
}]
}]
layout = go.Layout(
title=f'Archivo: EGRESOS_{year} CIE: {cie}',
mapbox_style='carto-positron',
mapbox_zoom=9.5,
mapbox_center = {'lat': 25.680, 'lon': -100.249},
sliders=sliders,
updatemenus=playbtn
)
data = frames[0]['data']
figure = go.Figure(data=data, layout=layout, frames=frames)
print(f'{filepath}: Guardando mapa en archivo...', flush=True)
# guardar mapa en archivo
plotly.offline.plot(figure, filename=filepath)