Py.Cafe

ptan6997aaa/

streamlit-on-pycafe-1

Streamlit on Py.cafe - Interactive Samples

DocsPricing
  • app.py
  • requirements.txt
  • schools.csv
  • uscities.csv
app.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
234
235
236
237
238
239
240
241
242
243
244
245
246
import pandas as pd
import streamlit as st
import folium
from streamlit_folium import folium_static
from colour import Color
import branca.colormap as cm

# Map Style Definitions
MAP_STYLES = {
    "Uses standard OpenStreetMap": "OpenStreetMap",
    "Carto Light": "CartoDB positron",
    "Carto Dark": "CartoDB dark_matter",
    "Carto Voyager": "CartoDB Voyager",
    "Esri Gray Canvas": None,  # Not directly available in Folium
    "OSM HOT": None,  # Not directly available in Folium
    "Esri NatGeo": "Esri.NatGeoWorldMap",
    "Esri Satellite": "Esri.WorldImagery",
    "Esri National Geographic": "Esri.WorldStreetMap",
    "OpenTopoMap Topography": "OpenTopoMap"
}

# ----------------------------
# 1. Data Loading and Preprocessing
# ----------------------------
@st.cache_data
def load_data():
    # Read school data
    df_schools = pd.read_csv("schools.csv")
    
    # Filter private schools (case insensitive)
    df_schools = df_schools[
        ~df_schools["description"].str.contains("private", case=False, na=False)
    ]
    
    # Extract city name (remove ", TX")
    df_schools["city"] = df_schools["city_state"].str.replace(r",\s*TX$", "", regex=True)
    
    # Remove rows with missing rankings
    df_schools = df_schools.dropna(subset=["rank_state_elementary"])
    
    # Ensure ranking is numeric
    df_schools["rank_state_elementary"] = pd.to_numeric(df_schools["rank_state_elementary"], errors="coerce")
    df_schools = df_schools.dropna(subset=["rank_state_elementary"])
    
    # Calculate city ranking (lower is better)
    df_schools["rank_city"] = (
        df_schools.groupby("city")["rank_state_elementary"]
        .rank(method="min", ascending=True)
    )
    
    # Read city coordinates (from simplemaps.com free version)
    df_cities = pd.read_csv("uscities.csv")
    tx_cities = df_cities[df_cities["state_id"] == "TX"][["city", "lat", "lng"]].copy()
    tx_cities["city"] = tx_cities["city"].str.title()
    
    # Merge coordinates to school data (for Top3 mode)
    df_schools = df_schools.merge(tx_cities, on="city", how="inner")
    
    # Precompute "All" mode: city school count
    school_counts = df_schools.groupby("city").size().reset_index(name="school_count")
    merged_all = school_counts.merge(tx_cities, on="city", how="inner")
    
    return df_schools, merged_all

# ----------------------------
# 2. Color Function (for All mode)
# ----------------------------
def get_color_count(value, min_val, max_val):
    if min_val == max_val:
        ratio = 0.5
    else:
        ratio = (value - min_val) / (max_val - min_val)
    
    start_color = Color("#4B0082")  # Dark Purple (Low values)
    end_color = Color("#FFD700")  # Bright Yellow (High values)
    return list(start_color.range_to(end_color, 100))[int(ratio * 99)].hex

# ----------------------------
# 3. Map Creation Functions
# ----------------------------
def create_all_mode_map(df_all, map_style_name):
    # Get the tile URL based on selection
    tile_url = MAP_STYLES.get(map_style_name, "CartoDB Voyager")
    
    # Handle special cases for map styles not directly supported in Folium
    if tile_url is None:
        tile_url = "CartoDB Voyager"
    
    # Create base map centered on Texas
    m = folium.Map(
        location=[31.9686, -99.9018],
        zoom_start=6,
        tiles=tile_url
    )
    
    if df_all.empty:
        return m
    
    # Calculate min and max for color scaling
    min_count = int(df_all["school_count"].min())
    max_count = int(df_all["school_count"].max())
    
    # Create color map legend
    colormap = cm.LinearColormap(
        colors=['#4B0082', '#FFD700'],
        vmin=min_count,
        vmax=max_count,
        caption='School Count'
    )
    m.add_child(colormap)
    
    # Radius Size Range Configuration For All Cities
    MIN_RADIUS = 3   # Minimum radius for cities with few schools
    MAX_RADIUS = 35  # Maximum radius for cities like Houston
    
    # Add markers for each city
    for _, row in df_all.iterrows():
        count = row['school_count']
        
        # Dynamic Radius Size Calculation For All Cities
        if max_count == min_count:
            radius = 10
        else:
            # Linear interpolation for size
            norm = (count - min_count) / (max_count - min_count)
            radius = MIN_RADIUS + (norm * (MAX_RADIUS - MIN_RADIUS))
        
        # Get color based on school count
        color = get_color_count(count, min_count, max_count)
        
        # Add circle marker
        folium.CircleMarker(
            location=[row["lat"], row["lng"]],
            radius=radius,
            color="white",
            weight=1,
            fillColor=color,
            fillOpacity=0.6,
            popup=f"{row['city']}: {int(row['school_count'])} school(s)"
        ).add_to(m)
    
    return m

def create_top3_mode_map(df_schools, map_style_name):
    # Get the tile URL based on selection
    tile_url = MAP_STYLES.get(map_style_name, "CartoDB Voyager")
    
    # Handle special cases for map styles not directly supported in Folium
    if tile_url is None:
        tile_url = "CartoDB Voyager"
    
    # Create base map centered on Texas
    m = folium.Map(
        location=[31.9686, -99.9018],
        zoom_start=6,
        tiles=tile_url
    )
    
    if df_schools.empty:
        return m
    
    # Get top 3 schools per city (highest state ranking)
    top3_df = (
        df_schools.groupby("city", group_keys=False)
        .apply(lambda g: g.nsmallest(3, "rank_state_elementary"))
        .reset_index(drop=True)
    )
    
    # Add markers for each city
    for city, city_df in top3_df.groupby("city"):
        # Sort by city ranking to ensure Top3 order
        city_df = city_df.sort_values("rank_city").head(3)
        
        lat, lng = city_df["lat"].iloc[0], city_df["lng"].iloc[0]
        
        # Create popup HTML
        popup_html = f"""
        <div>
            <strong>{city} (Top 3)</strong>
            <br>
        """
        
        for _, row in city_df.iterrows():
            rank_city_int = int(row["rank_city"])
            rank_state_int = int(row["rank_state_elementary"])
            popup_html += f"""
            <br>
            🏆 Top {rank_city_int}: <strong>{row['school_name']}</strong> | TX Rank #{rank_state_int}
            """
        
        popup_html += "</div>"
        
        # Add marker
        folium.CircleMarker(
            location=[lat, lng],
            radius=12,
            color="black",
            weight=2,
            fillColor="#FF8C00",
            fillOpacity=0.8,
            popup=folium.Popup(popup_html, max_width=300)
        ).add_to(m)
    
    return m

# ----------------------------
# 4. Streamlit App
# ----------------------------
def main():
    # Page title
    st.title("Texas Elementary Schools")
    
    # Load data
    df_schools, merged_all = load_data()
    
    # Control Panel (View Selector + Map Style Selector)
    col1, col2 = st.columns(2)
    
    with col1:
        # View Mode
        view_mode = st.selectbox(
            "Data View:",
            ["Overview (Bubble Map)", "Detailed (Top 3 Schools)"],
            index=0
        )
    
    with col2:
        # Map Background Style
        map_style_name = st.selectbox(
            "Map Background:",
            list(MAP_STYLES.keys()),
            index=2  # Default to Carto Voyager
        )
    
    # Create and display map based on view mode
    if view_mode == "Overview (Bubble Map)":
        st.header("Overview - School Count by City")
        m = create_all_mode_map(merged_all, map_style_name)
        folium_static(m, width=700, height=500)
    else:
        st.header("Detailed - Top 3 Schools by City")
        m = create_top3_mode_map(df_schools, map_style_name)
        folium_static(m, width=700, height=500)

if __name__ == "__main__":
    main()