File size: 9,032 Bytes
1aa7583
 
 
 
 
 
 
 
1c76a96
 
 
2236dd4
 
1c76a96
 
 
cc8b36c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c76a96
 
263f645
 
 
1aa7583
66fa388
1c76a96
46725fa
 
 
46bc3f8
 
690fe2e
46bc3f8
 
 
 
46725fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46bc3f8
7d06004
 
 
4501539
7d06004
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1aa7583
 
7d06004
 
 
1aa7583
 
 
 
 
46bc3f8
 
 
690fe2e
 
 
46bc3f8
690fe2e
 
 
 
 
 
46bc3f8
690fe2e
 
 
 
 
 
 
 
 
 
 
46bc3f8
 
 
1aa7583
 
 
656f25c
8424379
 
 
46bc3f8
7d06004
 
8424379
 
 
 
 
46bc3f8
 
 
690fe2e
 
 
 
 
 
 
 
 
 
 
 
 
46bc3f8
690fe2e
 
 
 
 
 
46bc3f8
690fe2e
46bc3f8
 
8424379
 
 
 
46bc3f8
 
1aa7583
7d06004
46bc3f8
1aa7583
46bc3f8
1aa7583
46bc3f8
1aa7583
8424379
7d06004
 
 
 
 
02c6b99
7d06004
 
 
 
 
8424379
46bc3f8
7d06004
8424379
46bc3f8
8424379
46bc3f8
8424379
46bc3f8
46725fa
46bc3f8
656f25c
46725fa
 
 
 
 
 
 
 
 
d1dca6b
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import ibis
from ibis import _
import streamlit as st

st.set_page_config(layout="wide",
                   page_title="TPL LandVote",
                   page_icon=":globe:")

'''
# LandVote Prototype

An experimental platform for visualizing data on ballot measures for conservation, based on data from <https://landvote.org/> curated by the Trust for Public Land.

'''


## Chatbot
import os
import pandas as pd
import matplotlib.pyplot as plt
from pandasai.llm.openai import OpenAI
from pandasai import Agent
from pandasai.responses.streamlit_response import StreamlitResponse

llm = OpenAI(api_token=st.secrets["OPENAI_API_KEY"])
df1 = pd.read_csv("data.csv")

agent = Agent(
    [df1],
    config={"verbose": True, "response_parser": StreamlitResponse, "llm": llm},
)



with st.sidebar:

    '''
    ## Data Assistant (experimental)

    Ask questions about the landvote data, like:

    - What are the top states for approved conservation funds?
    - Plot the total funds spent in conservation each year.
    - What city has approved the most funds in a single measure? What was the description of that vote?
    - Which state has had largest number measures fail? What is that as a fraction of it's total measures?
    '''
    
    prompt = st.chat_input("Ask about the data")
    if prompt:
        with st.spinner():
            resp = agent.chat(prompt)
            if os.path.isfile('exports/charts/temp_chart.png'):
                im = plt.imread('exports/charts/temp_chart.png')
                st.image(im)
                os.remove('exports/charts/temp_chart.png')
            st.write(resp)
        


# year = st.slider("Select a year", min_value=1988, max_value=2024, value=2022, step=2)
year = st.slider("Select a year", min_value=1988, max_value=2024, value=2022, step=1)

import leafmap.maplibregl as leafmap
m = leafmap.Map(style="positron",   center=(-100, 40), zoom=3)

# url = "https://huggingface.co/datasets/boettiger-lab/landvote/resolve/main/landvote_polygons.pmtiles"
url = "https://huggingface.co/datasets/boettiger-lab/landvote/resolve/main/votes.pmtiles"
parties = "https://huggingface.co/datasets/boettiger-lab/landvote/resolve/main/votes.parquet"

dark_orange = 'rgba(171, 86, 1, 1)'   # dark orange - min value 
light_orange = 'rgba(243, 211, 177, 1)' # light orange 
grey = 'rgba(211, 211, 211, 1)'        # grey
light_green = 'rgba(195, 219, 195, 1)' # light green 
dark_green = 'rgba(65, 125, 65, 1)'    # dark green  - max value 

con = ibis.duckdb.connect(extensions=["spatial"])

party = (con
            .read_parquet(parties)
            .cast({"geometry": "geometry"})
            )

    
def get_summary(party, year):
    total_measures = party.filter(_.year == year).count().execute()
    
    df = (party
        .filter(_.year == year)
        .mutate(
            # Convert 'amount' from string with '$' and ',' to numeric
            amount_numeric=_.amount.replace('$', '').replace(',', '').cast('float64')
        )
        .group_by("party")
        .aggregate(
            percent_passed= (_.Status.isin(["Pass", "Pass*"]).sum() / total_measures).round(2),
            approved_funds= ibis.case()
                            .when(_.Status.isin(["Pass", "Pass*"]), _.amount_numeric)
                            .else_(ibis.literal(0))
                            .end()
                            .sum()
        )
        .mutate(color=ibis.case()
            .when(_.party == "DEMOCRAT", ibis.literal("#083A90"))
            .else_(ibis.literal("#E81B23"))
            .end())
    )
    
    df = df.to_pandas()
    return df

style_municipals = {
    "layers": [
        {
            "id": "cities",
            "source": "municipal",
            "source-layer": "municipal",
            "type": "fill-extrusion",
            "filter": [
                "==",
                ["get", "year"],
                year,
            ],
            "paint": {
                "fill-extrusion-color": [
                    "case",
                    # if passed, color green
                    ["==", ["get", "Status"], "Pass"],
                    [
                        "interpolate", ["linear"], [
                            "to-number", ["slice", ["get", "yes"], 0, -1]  # convert 'yes' string to number
                        ],
                        50, grey,    
                        55, light_green,     # higher yes % -> darker green
                        100, dark_green     # 100 is the max of data 
                    ],
                    # if failed, color orange
                    ["==", ["get", "Status"], "Fail"],
                    [
                        "interpolate", ["linear"], [
                            "to-number", ["slice", ["get", "yes"], 0, -1]  # convert 'yes' string to number
                        ],
                        0, dark_orange,     # higher yes % -> lighter orange 
                        50, light_orange,   
                        67, grey # 67 is the max of data.
                    ],
                    grey  # if no match 
                ],
                "fill-extrusion-height": ["*", ["get", "log_amount"], 5000],
            }
        },
    ],
}

style_counties = {
    "layers": [
        {
            "id": "counties",
            "source": "county",
            "source-layer": "county",
            "type": "fill-extrusion",
            "filter": [
                "==",
                ["get", "year"],
                year,
            ],
            "paint": {
                "fill-extrusion-color": [
                    "case",
                    # if passed, color green
                    ["==", ["get", "Status"], "Pass"],
                    [
                        "interpolate", ["linear"], [
                            "to-number", ["slice", ["get", "yes"], 0, -1]  # convert 'yes' string to number
                        ],
                        50, grey,    
                        55, light_green,     # higher yes % -> darker green
                        100, dark_green     # 100 is the max of data 
                    ],
                    # if failed, color orange
                    ["==", ["get", "Status"], "Fail"],
                    [
                        "interpolate", ["linear"], [
                            "to-number", ["slice", ["get", "yes"], 0, -1]  # convert 'yes' string to number
                        ],
                        0, dark_orange,     # higher yes % -> lighter orange 
                        50, light_orange,   
                        67, grey # 67 is the max of data.
                    ],
                    grey  # if no match 
                ],
                "fill-extrusion-height": ["*", ["get", "log_amount"], 5000],
            }
        },
    ],
}

style_states = {
    "layers": [
        {
            "id": "states",
            "source": "state",
            "source-layer": "state",
            "type": "fill",
            "filter": [
                "==",
                ["get", "year"],
                year,
            ],
            "paint": {
                "fill-color": [
                    "case",
                    # if passed, color green
                    ["==", ["get", "Status"], "Pass"],
                    [
                        "interpolate", ["linear"], [
                            "to-number", ["slice", ["get", "yes"], 0, -1]  # convert 'yes' string to number
                        ],
                        50, grey,    
                        55, light_green,     # higher yes % -> darker green
                        100, dark_green     # 100 is the max of data 
                    ],
                    # if failed, color orange
                    ["==", ["get", "Status"], "Fail"],
                    [
                        "interpolate", ["linear"], [
                            "to-number", ["slice", ["get", "yes"], 0, -1]  # convert 'yes' string to number
                        ],
                        0, dark_orange,     # higher yes % -> lighter orange 
                        50, light_orange,   
                        67, grey # 67 is the max of data.
                    ],
                    grey  # if no match 
                ]
            }
        },
    ],
}


#states are 2D and transparent, thus added separately.
m.add_pmtiles(
    url,
    style=style_states,
    visible=True,
    opacity=0.6,
    tooltip=True,
    fit_bounds=False
)

#states are 2D and transparent, thus added separately.
m.add_pmtiles(
    url,
    style=style_counties,
    visible=True,
    opacity=1.0,
    tooltip=True,
    fit_bounds=False
)


m.add_pmtiles(
    url,
    style=style_municipals,
    visible=True,
    opacity=1.0,
    tooltip=True,
    fit_bounds=False
)


m.add_layer_control()
m.to_streamlit()
# st.dataframe(df)

df = get_summary(party, year)
st.bar_chart(df, x= "party",y = "percent_passed",color="color")

st.bar_chart(df, x= "party",y = "approved_funds",color="color")

# st.divider()
# footer = st.container()