Spaces:
Sleeping
Sleeping
cassiebuhler
commited on
Commit
•
45a8c07
1
Parent(s):
36961ed
adding color shading options
Browse files
app.py
CHANGED
@@ -2,6 +2,10 @@ import ibis
|
|
2 |
from ibis import _
|
3 |
import streamlit as st
|
4 |
|
|
|
|
|
|
|
|
|
5 |
st.set_page_config(layout="wide",
|
6 |
page_title="TPL LandVote",
|
7 |
page_icon=":globe:")
|
@@ -13,6 +17,14 @@ An experimental platform for visualizing data on ballot measures for conservatio
|
|
13 |
|
14 |
'''
|
15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
## Chatbot
|
18 |
import os
|
@@ -34,6 +46,7 @@ agent = Agent(
|
|
34 |
|
35 |
with st.sidebar:
|
36 |
|
|
|
37 |
'''
|
38 |
## Data Assistant (experimental)
|
39 |
|
@@ -57,22 +70,14 @@ with st.sidebar:
|
|
57 |
|
58 |
|
59 |
|
|
|
60 |
# year = st.slider("Select a year", min_value=1988, max_value=2024, value=2022, step=2)
|
61 |
year = st.slider("Select a year", min_value=1988, max_value=2024, value=2022, step=1)
|
62 |
|
63 |
-
import leafmap.maplibregl as leafmap
|
64 |
-
m = leafmap.Map(style="positron", center=(-100, 40), zoom=3)
|
65 |
|
66 |
-
# url = "https://huggingface.co/datasets/boettiger-lab/landvote/resolve/main/landvote_polygons.pmtiles"
|
67 |
url = "https://huggingface.co/datasets/boettiger-lab/landvote/resolve/main/votes.pmtiles"
|
68 |
parties = "https://huggingface.co/datasets/boettiger-lab/landvote/resolve/main/votes.parquet"
|
69 |
|
70 |
-
dark_orange = 'rgba(171, 86, 1, 1)' # dark orange - min value
|
71 |
-
light_orange = 'rgba(243, 211, 177, 1)' # light orange
|
72 |
-
grey = 'rgba(211, 211, 211, 1)' # grey
|
73 |
-
light_green = 'rgba(195, 219, 195, 1)' # light green
|
74 |
-
dark_green = 'rgba(65, 125, 65, 1)' # dark green - max value
|
75 |
-
|
76 |
con = ibis.duckdb.connect(extensions=["spatial"])
|
77 |
|
78 |
party = (con
|
@@ -82,6 +87,7 @@ party = (con
|
|
82 |
|
83 |
def get_passes(party):
|
84 |
df = (party
|
|
|
85 |
.group_by("year", "party")
|
86 |
.aggregate(total=_.count(),
|
87 |
passes = (_.Status.isin(["Pass", "Pass*"]).sum())
|
@@ -93,62 +99,13 @@ def get_passes(party):
|
|
93 |
.else_(ibis.literal("#E81B23"))
|
94 |
.end()
|
95 |
)
|
96 |
-
# # .select("year","party","percent_passed","color")
|
97 |
-
)
|
98 |
-
|
99 |
-
df = df.to_pandas()
|
100 |
-
return df
|
101 |
-
|
102 |
-
def get_summary(party, year):
|
103 |
-
total_measures = party.filter(_.year == year).count().execute()
|
104 |
-
|
105 |
-
df = (party
|
106 |
-
.filter(_.year == year)
|
107 |
-
.mutate(
|
108 |
-
# Convert 'amount' from string with '$' and ',' to numeric
|
109 |
-
# amount_numeric=_.amount.replace('$', '').replace(',', '').cast('float64')
|
110 |
-
)
|
111 |
-
.group_by("party")
|
112 |
-
.aggregate(
|
113 |
-
total = _.count(),
|
114 |
-
percent_passed= (_.Status.isin(["Pass", "Pass*"]).sum() / _.count()).round(2),
|
115 |
-
)
|
116 |
-
.mutate(color=ibis.case()
|
117 |
-
.when(_.party == "DEMOCRAT", ibis.literal("#92c7f5"))
|
118 |
-
.else_(ibis.literal("#E81B23"))
|
119 |
-
.end())
|
120 |
)
|
121 |
|
122 |
df = df.to_pandas()
|
123 |
return df
|
124 |
-
|
125 |
-
# def get_cumulative(party):
|
126 |
-
|
127 |
-
# df = (party
|
128 |
-
# .select("year","amount","Status")
|
129 |
-
# # .mutate(
|
130 |
-
# # # Convert 'amount' from string with '$' and ',' to numeric
|
131 |
-
# # # amount_numeric=_.amount.replace('$', '').replace(',', '').cast('float64')
|
132 |
-
# # )
|
133 |
-
# # .group_by("party")
|
134 |
-
# # .aggregate(
|
135 |
-
# # total = _.count(),
|
136 |
-
# # percent_passed= (_.Status.isin(["Pass", "Pass*"]).sum() / _.count()).round(2),
|
137 |
-
# # )
|
138 |
-
# # .mutate(color=ibis.case()
|
139 |
-
# # .when(_.party == "DEMOCRAT", ibis.literal("#92c7f5"))
|
140 |
-
# # .else_(ibis.literal("#E81B23"))
|
141 |
-
# # .end())
|
142 |
-
# )
|
143 |
-
|
144 |
-
# df = df.to_pandas()
|
145 |
-
# return df
|
146 |
-
|
147 |
|
148 |
-
import altair as alt
|
149 |
-
import streamlit as st
|
150 |
|
151 |
-
def
|
152 |
chart = alt.Chart(df_passes).mark_line(strokeWidth=3).encode(
|
153 |
x=alt.X('year:N', title='Year'),
|
154 |
y=alt.Y('percent_passed:Q', title='Percent Passed'),
|
@@ -164,23 +121,20 @@ def plot(df_passes):
|
|
164 |
st.altair_chart(chart, use_container_width=True)
|
165 |
|
166 |
|
167 |
-
|
168 |
-
|
169 |
def funding_chart(party):
|
170 |
-
|
171 |
df = (party
|
172 |
.mutate(amount=_.amount.replace('$', '').replace(',', '').cast('float64'))
|
173 |
.filter(_.Status.isin(["Pass", "Pass*"]))
|
174 |
.group_by("year")
|
175 |
.aggregate(total_funding=_.amount.sum())
|
176 |
.order_by("year")
|
177 |
-
.mutate(cumulative_funding=_.total_funding.cumsum())
|
178 |
.execute()
|
179 |
)
|
180 |
|
181 |
chart = alt.Chart(df).mark_line(strokeWidth=3).encode(
|
182 |
x=alt.X('year:N', title='Year'),
|
183 |
-
y=alt.Y('cumulative_funding:Q', title='
|
184 |
).properties(
|
185 |
title='Cumulative Funding'
|
186 |
)
|
@@ -188,21 +142,8 @@ def funding_chart(party):
|
|
188 |
st.altair_chart(chart, use_container_width=True)
|
189 |
|
190 |
|
191 |
-
|
192 |
-
|
193 |
-
"layers": [
|
194 |
-
{
|
195 |
-
"id": "cities",
|
196 |
-
"source": "municipal",
|
197 |
-
"source-layer": "municipal",
|
198 |
-
"type": "fill-extrusion",
|
199 |
-
"filter": [
|
200 |
-
"==",
|
201 |
-
["get", "year"],
|
202 |
-
year,
|
203 |
-
],
|
204 |
-
"paint": {
|
205 |
-
"fill-extrusion-color": [
|
206 |
"case",
|
207 |
# if passed, color green
|
208 |
["==", ["get", "Status"], "Pass"],
|
@@ -225,26 +166,11 @@ style_municipals = {
|
|
225 |
67, grey # 67 is the max of data.
|
226 |
],
|
227 |
grey # if no match
|
228 |
-
]
|
229 |
-
"fill-extrusion-height": ["*", ["get", "log_amount"], 5000],
|
230 |
}
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
style_counties = {
|
236 |
-
"layers": [
|
237 |
-
{
|
238 |
-
"id": "counties",
|
239 |
-
"source": "county",
|
240 |
-
"source-layer": "county",
|
241 |
-
"type": "fill-extrusion",
|
242 |
-
"filter": [
|
243 |
-
"==",
|
244 |
-
["get", "year"],
|
245 |
-
year,
|
246 |
-
],
|
247 |
-
"paint": {
|
248 |
"fill-extrusion-color": [
|
249 |
"case",
|
250 |
# if passed, color green
|
@@ -271,100 +197,166 @@ style_counties = {
|
|
271 |
],
|
272 |
"fill-extrusion-height": ["*", ["get", "log_amount"], 5000],
|
273 |
}
|
274 |
-
},
|
275 |
-
],
|
276 |
-
}
|
277 |
-
|
278 |
-
style_states = {
|
279 |
-
"layers": [
|
280 |
-
{
|
281 |
-
"id": "states",
|
282 |
-
"source": "state",
|
283 |
-
"source-layer": "state",
|
284 |
-
"type": "fill",
|
285 |
-
"filter": [
|
286 |
-
"==",
|
287 |
-
["get", "year"],
|
288 |
-
year,
|
289 |
-
],
|
290 |
-
"paint": {
|
291 |
-
"fill-color": [
|
292 |
-
"case",
|
293 |
-
# if passed, color green
|
294 |
-
["==", ["get", "Status"], "Pass"],
|
295 |
-
[
|
296 |
-
"interpolate", ["linear"], [
|
297 |
-
"to-number", ["slice", ["get", "yes"], 0, -1] # convert 'yes' string to number
|
298 |
-
],
|
299 |
-
50, grey,
|
300 |
-
55, light_green, # higher yes % -> darker green
|
301 |
-
100, dark_green # 100 is the max of data
|
302 |
-
],
|
303 |
-
# if failed, color orange
|
304 |
-
["==", ["get", "Status"], "Fail"],
|
305 |
-
[
|
306 |
-
"interpolate", ["linear"], [
|
307 |
-
"to-number", ["slice", ["get", "yes"], 0, -1] # convert 'yes' string to number
|
308 |
-
],
|
309 |
-
0, dark_orange, # higher yes % -> lighter orange
|
310 |
-
50, light_orange,
|
311 |
-
67, grey # 67 is the max of data.
|
312 |
-
],
|
313 |
-
grey # if no match
|
314 |
-
]
|
315 |
-
}
|
316 |
-
},
|
317 |
-
],
|
318 |
-
}
|
319 |
|
320 |
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
340 |
|
|
|
341 |
|
342 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
343 |
url,
|
344 |
-
style=
|
345 |
visible=True,
|
346 |
-
opacity=
|
347 |
tooltip=True,
|
348 |
fit_bounds=False
|
349 |
-
)
|
350 |
-
|
351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
m.add_layer_control()
|
|
|
353 |
m.to_streamlit()
|
354 |
|
355 |
df_passes = get_passes(party)
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
df = get_summary(party, year)
|
361 |
-
|
362 |
-
# st.line_chart(df_passes, x= "year",y = "percent_passed",color="color")
|
363 |
-
|
364 |
-
# st.bar_chart(df, x= "party",y = "percent_passed",color="color")
|
365 |
-
# Assuming df is your dataframe with 'year', 'amount', and 'Status' columns
|
366 |
-
# df_fund = get_cumulative(party)
|
367 |
-
funding_chart(party)
|
368 |
|
369 |
|
370 |
# st.divider()
|
|
|
2 |
from ibis import _
|
3 |
import streamlit as st
|
4 |
|
5 |
+
import altair as alt
|
6 |
+
|
7 |
+
|
8 |
+
|
9 |
st.set_page_config(layout="wide",
|
10 |
page_title="TPL LandVote",
|
11 |
page_icon=":globe:")
|
|
|
17 |
|
18 |
'''
|
19 |
|
20 |
+
dark_orange = 'rgba(171, 86, 1, 1)' # dark orange - min value
|
21 |
+
light_orange = 'rgba(243, 211, 177, 1)' # light orange
|
22 |
+
grey = 'rgba(211, 211, 211, 1)' # grey
|
23 |
+
light_green = 'rgba(195, 219, 195, 1)' # light green
|
24 |
+
dark_green = 'rgba(65, 125, 65, 1)' # dark green - max value
|
25 |
+
|
26 |
+
dem_blue = "#1b46c2"
|
27 |
+
rep_red = "#E81B23"
|
28 |
|
29 |
## Chatbot
|
30 |
import os
|
|
|
46 |
|
47 |
with st.sidebar:
|
48 |
|
49 |
+
|
50 |
'''
|
51 |
## Data Assistant (experimental)
|
52 |
|
|
|
70 |
|
71 |
|
72 |
|
73 |
+
|
74 |
# year = st.slider("Select a year", min_value=1988, max_value=2024, value=2022, step=2)
|
75 |
year = st.slider("Select a year", min_value=1988, max_value=2024, value=2022, step=1)
|
76 |
|
|
|
|
|
77 |
|
|
|
78 |
url = "https://huggingface.co/datasets/boettiger-lab/landvote/resolve/main/votes.pmtiles"
|
79 |
parties = "https://huggingface.co/datasets/boettiger-lab/landvote/resolve/main/votes.parquet"
|
80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
con = ibis.duckdb.connect(extensions=["spatial"])
|
82 |
|
83 |
party = (con
|
|
|
87 |
|
88 |
def get_passes(party):
|
89 |
df = (party
|
90 |
+
.filter(_.year >= 2000)
|
91 |
.group_by("year", "party")
|
92 |
.aggregate(total=_.count(),
|
93 |
passes = (_.Status.isin(["Pass", "Pass*"]).sum())
|
|
|
99 |
.else_(ibis.literal("#E81B23"))
|
100 |
.end()
|
101 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
)
|
103 |
|
104 |
df = df.to_pandas()
|
105 |
return df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
|
|
|
|
|
107 |
|
108 |
+
def percent_chart(df_passes):
|
109 |
chart = alt.Chart(df_passes).mark_line(strokeWidth=3).encode(
|
110 |
x=alt.X('year:N', title='Year'),
|
111 |
y=alt.Y('percent_passed:Q', title='Percent Passed'),
|
|
|
121 |
st.altair_chart(chart, use_container_width=True)
|
122 |
|
123 |
|
|
|
|
|
124 |
def funding_chart(party):
|
|
|
125 |
df = (party
|
126 |
.mutate(amount=_.amount.replace('$', '').replace(',', '').cast('float64'))
|
127 |
.filter(_.Status.isin(["Pass", "Pass*"]))
|
128 |
.group_by("year")
|
129 |
.aggregate(total_funding=_.amount.sum())
|
130 |
.order_by("year")
|
131 |
+
.mutate(cumulative_funding=_.total_funding.cumsum()/1e9)
|
132 |
.execute()
|
133 |
)
|
134 |
|
135 |
chart = alt.Chart(df).mark_line(strokeWidth=3).encode(
|
136 |
x=alt.X('year:N', title='Year'),
|
137 |
+
y=alt.Y('cumulative_funding:Q', title='Billions of Dollars'),
|
138 |
).properties(
|
139 |
title='Cumulative Funding'
|
140 |
)
|
|
|
142 |
st.altair_chart(chart, use_container_width=True)
|
143 |
|
144 |
|
145 |
+
paint_fill = {
|
146 |
+
"fill-color": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
147 |
"case",
|
148 |
# if passed, color green
|
149 |
["==", ["get", "Status"], "Pass"],
|
|
|
166 |
67, grey # 67 is the max of data.
|
167 |
],
|
168 |
grey # if no match
|
169 |
+
]
|
|
|
170 |
}
|
171 |
+
|
172 |
+
|
173 |
+
paint_extrusion = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
"fill-extrusion-color": [
|
175 |
"case",
|
176 |
# if passed, color green
|
|
|
197 |
],
|
198 |
"fill-extrusion-height": ["*", ["get", "log_amount"], 5000],
|
199 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
|
201 |
|
202 |
+
def get_style_status(jurisdiction):
|
203 |
+
if jurisdiction == "State":
|
204 |
+
name = "state"
|
205 |
+
label = "states"
|
206 |
+
paint_type = paint_fill
|
207 |
+
layer_type = "fill"
|
208 |
+
|
209 |
+
elif jurisdiction == "County":
|
210 |
+
name = "county"
|
211 |
+
label = "counties"
|
212 |
+
paint_type = paint_extrusion
|
213 |
+
layer_type = "fill-extrusion"
|
214 |
+
|
215 |
+
else:
|
216 |
+
name = "municipal"
|
217 |
+
label = "cities"
|
218 |
+
paint_type = paint_extrusion
|
219 |
+
layer_type = "fill-extrusion"
|
220 |
+
|
221 |
+
print(paint_type)
|
222 |
+
style = {
|
223 |
+
"layers": [
|
224 |
+
{
|
225 |
+
"id": label,
|
226 |
+
"source": name,
|
227 |
+
"source-layer": name,
|
228 |
+
"type": layer_type,
|
229 |
+
"filter": [
|
230 |
+
"==",
|
231 |
+
["get", "year"],
|
232 |
+
year,
|
233 |
+
],
|
234 |
+
"paint": paint_type
|
235 |
+
},
|
236 |
+
],
|
237 |
+
}
|
238 |
+
return style
|
239 |
+
|
240 |
+
|
241 |
+
def get_style_party(jurisdiction):
|
242 |
+
if jurisdiction == "State":
|
243 |
+
name = "state"
|
244 |
+
label = "states"
|
245 |
+
elif jurisdiction == "County":
|
246 |
+
name = "county"
|
247 |
+
label = "counties"
|
248 |
+
|
249 |
+
else:
|
250 |
+
name = "municipal"
|
251 |
+
label = "cities"
|
252 |
+
|
253 |
+
|
254 |
+
style_party = {
|
255 |
+
"layers": [
|
256 |
+
{
|
257 |
+
"id": label,
|
258 |
+
"source": name,
|
259 |
+
"source-layer": name,
|
260 |
+
"type": "fill",
|
261 |
+
"filter": [
|
262 |
+
"==",
|
263 |
+
["get", "year"],
|
264 |
+
year,
|
265 |
+
],
|
266 |
+
"paint": {
|
267 |
+
"fill-color":
|
268 |
+
{
|
269 |
+
'property': 'party',
|
270 |
+
'type': 'categorical',
|
271 |
+
'stops': [
|
272 |
+
["DEMOCRAT", dem_blue],
|
273 |
+
["REPUBLICAN", rep_red],
|
274 |
+
]
|
275 |
+
}
|
276 |
+
}
|
277 |
+
},
|
278 |
+
],
|
279 |
+
}
|
280 |
+
return style_party
|
281 |
+
|
282 |
+
|
283 |
+
|
284 |
+
style_options= ["Measure Status", "Political Party"]
|
285 |
+
|
286 |
+
color_choice = st.radio("Color by:", style_options)
|
287 |
+
|
288 |
+
import leafmap.maplibregl as leafmap
|
289 |
+
m = leafmap.Map(style="positron", center=(-100, 40), zoom=3)
|
290 |
|
291 |
+
if color_choice == "Measure Status":
|
292 |
|
293 |
+
#states are 2D and transparent, thus added separately.
|
294 |
+
m.add_pmtiles(
|
295 |
+
url,
|
296 |
+
style=get_style_status("States"),
|
297 |
+
visible=True,
|
298 |
+
opacity=0.6,
|
299 |
+
tooltip=True,
|
300 |
+
fit_bounds=False
|
301 |
+
)
|
302 |
+
|
303 |
+
#states are 2D and transparent, thus added separately.
|
304 |
+
m.add_pmtiles(
|
305 |
+
url,
|
306 |
+
style=get_style_status("County"),
|
307 |
+
visible=True,
|
308 |
+
opacity=1.0,
|
309 |
+
tooltip=True,
|
310 |
+
fit_bounds=False
|
311 |
+
)
|
312 |
+
|
313 |
+
|
314 |
+
m.add_pmtiles(
|
315 |
+
url,
|
316 |
+
style=get_style_status("Municipal"),
|
317 |
+
visible=True,
|
318 |
+
opacity=1.0,
|
319 |
+
tooltip=True,
|
320 |
+
fit_bounds=False
|
321 |
+
)
|
322 |
+
|
323 |
+
else:
|
324 |
+
m.add_pmtiles(
|
325 |
url,
|
326 |
+
style=get_style_party("States"),
|
327 |
visible=True,
|
328 |
+
opacity=0.6,
|
329 |
tooltip=True,
|
330 |
fit_bounds=False
|
331 |
+
)
|
332 |
+
|
333 |
+
m.add_pmtiles(
|
334 |
+
url,
|
335 |
+
style=get_style_party("County"),
|
336 |
+
visible=True,
|
337 |
+
opacity=1.0,
|
338 |
+
tooltip=True,
|
339 |
+
fit_bounds=False
|
340 |
+
)
|
341 |
+
|
342 |
+
|
343 |
+
m.add_pmtiles(
|
344 |
+
url,
|
345 |
+
style=get_style_party("Municipal"),
|
346 |
+
visible=True,
|
347 |
+
opacity=1.0,
|
348 |
+
tooltip=True,
|
349 |
+
fit_bounds=False
|
350 |
+
)
|
351 |
+
|
352 |
+
|
353 |
m.add_layer_control()
|
354 |
+
|
355 |
m.to_streamlit()
|
356 |
|
357 |
df_passes = get_passes(party)
|
358 |
+
percent_chart(df_passes)
|
359 |
+
funding_chart(party.filter(_.year >= 2000))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
360 |
|
361 |
|
362 |
# st.divider()
|