CoolFace
Apppublic

DatavizGroup/Final_version01

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
streamlit_app.py609 linesDownload Raw Back to src
1# Imports.2import streamlit as st3import seaborn as sns4import pandas as pd5import matplotlib.pyplot as plt6import altair as alt7from pathlib import Path8import plotly.express as px9import geopandas as gpd10import folium11from streamlit_folium import st_folium12import json13import os14from pathlib import Path15import circlify16import plotly.graph_objects as go17 18# point Streamlit at a writable folder19os.environ["STREAMLIT_CONFIG_DIR"] = "/tmp/.streamlit"20Path(os.environ["STREAMLIT_CONFIG_DIR"]).mkdir(parents=True, exist_ok=True)21 22 23# Define paths.24# Path for geo_json.25GEOJSON_PATH = Path(__file__).parent / "County_Boundary.geojson"26 27# Handle the error for geo_json.28try:29    gdf_counties = gpd.read_file(GEOJSON_PATH)30except FileNotFoundError:31    st.error("Error: 'County_Boundary.geojson' file not found in the /app/src/ directory. Please ensure the file is included in the project.")32    st.stop()33 34# Path for crime data.35DATA_PATH = Path(__file__).parent / "crime_data.csv"        # /app/src/crime_data.csv36REGION_DATA_PATH = Path(__file__).parent / "area_lookup.csv"37 38# ── 0. Page configuration ──39st.set_page_config(40    page_title="Analyze Crime Distributions", 41    page_icon="📊", 42    layout="wide"43)44st.markdown("""45        <style>46        .title {47            text-align: center;48            padding: 25px;49            color: #2c3e50;50            font-family: 'Source Sans Pro', sans-serif;51        }52        /* Paragraph/write-up styling */53      .description {54        font-size: 18px;           /* comfortable reading size */55        line-height: 1.6;          /* good spacing */56        color: #4b4b4b;            /* dark grey text */57        text-align: justify;       /* nice full-justified look */58        padding: 0 10px 20px;      /* side & bottom padding */59        font-family: 'Helvetica Neue', Arial, sans-serif;60      }61      .sectionheader {62          font-family: 'Source Sans Pro', sans-serif;63          font-size: 32px;64          color: #2c3e50;65          margin-top: 15px;66          margin-bottom: 10px;67          border-bottom: 3px solid #ccc;68          padding-bottom: 8px;69        }70        </style>71        """, unsafe_allow_html=True)72 73# 1. Page title74st.markdown("<div class='title'><h1> 🚔 Crime Pulse: LAPD Incident Explorer 🔎 </h1></div>", unsafe_allow_html=True)75st.markdown("<div class='title'><h3>Group 9: Vighnesh Gosavi, Vivian Lin, Chun-Wen Liou, Shivam Patel, Jinwen Zhang</h3></div>", unsafe_allow_html=True)76    77st.markdown("""<div class='description'> This application provides a suite of interactive visualizations—pie charts, 78bar charts, scatter plots, and more—that let you explore crime patterns in the LAPD dataset from multiple angles. 79Quickly see which offense categories dominate, compare arrest rates against non-arrests, track how crime volumes change over time, and examine geographic hotspots. 80These insights can help police departments, community organizations, and policymakers allocate resources more effectively and 81design targeted strategies to improve public safety.</div>""",unsafe_allow_html=True)82 83# 2. Data info & load84st.markdown("<div class='sectionheader'> Dataset Information </div>", unsafe_allow_html=True)85st.markdown(86    """87    <div class="description">88      <ul>89        <li><strong>Source:</strong> LAPD crime incidents dataset</li>90        <li><strong>Rows:</strong> one incident per row</li>91        <li><strong>Columns:</strong> e.g. <code>crm_cd_desc</code> (crime type), <code>arrest</code> (boolean), <code>year</code>, <code>location_description</code>, etc.</li>92        <li><strong>Purpose:</strong> Interactive exploration of top crime categories and arrest rates.</li>93      </ul>94    </div>95    """,96    unsafe_allow_html=True97)98 99# # Define paths.100# # Path for geo_json.101# GEOJSON_PATH = Path(__file__).parent / "County_Boundary.geojson"102 103# # Handle the error for geo_json.104# try:105#     gdf_counties = gpd.read_file(GEOJSON_PATH)106# except FileNotFoundError:107#     st.error("Error: 'County_Boundary.geojson' file not found in the /app/src/ directory. Please ensure the file is included in the project.")108#     st.stop()109 110# # Path for crime data.111# DATA_PATH = Path(__file__).parent / "crime_data.csv"        # /app/src/crime_data.csv112 113@st.cache_data114def load_data():115    return pd.read_csv(DATA_PATH)116def region_load_data():117    return pd.read_csv(REGION_DATA_PATH)    118    119if st.button("🔄 Refresh Data"):120        st.cache_data.clear()  # Clear the cache121        st.toast("Data is refreshed",icon="✅")  # Reload the data122        123# 2. Load and early‐exit if missing124df = load_data()125lookup = region_load_data()126map_region   = dict(zip(lookup["OBJECTID"], lookup["APREC"]))127map_precinct = dict(zip(lookup["OBJECTID"], lookup["PREC"]))128 129if df.empty:130    st.stop()131 132# Map into new columns133df["RegionName"]   = df["area"].map(map_region)134df["PrecinctCode"] = df["area"].map(map_precinct)135 136# 3. Data preview137st.markdown("<div class='sectionheader'> Data Preview </div>", unsafe_allow_html=True)138st.markdown(139    f"<div class='description'>"140    f"Total records: <strong>{df.shape[0]:,}</strong> &nbsp;|&nbsp; "141    f"Total columns: <strong>{df.shape[1]:,}</strong>"142    f"</div>",143    unsafe_allow_html=True144)145st.dataframe(df.head())146 147# Pie Chart 1: Top 10 Crime Types148st.markdown("<div class='sectionheader'> Top 10 Crime Types by Year </div>", unsafe_allow_html=True)149    150years = sorted(df["year"].dropna().astype(int).unique())151# Prepend an “All” option152options = ["All"] + years153selected_year = st.selectbox("Select Year", options, index=0)    154# # Year filter (shorter, above chart)155# col_empty, col_filter = st.columns([3,1])156# with col_filter:157#     selected_year = st.selectbox(158#         "Select Year",159#         options=options,160#         index=0,  # default to “All”161#         key="year_filter"162#     )163    164# Filter according to selection165if selected_year == "All":166    filtered = df.copy()167else:168    filtered = df[df["year"] == selected_year]169    170# Compute top 10 crime types for that year ──171top_crimes = (172    filtered["crm_cd_desc"]173        .value_counts()174        .nlargest(10)175        .rename_axis("Crime Type")176        .reset_index(name="Count")177)178top_crimes["Percentage"] = top_crimes["Count"] / top_crimes["Count"].sum()179    180#Key Metrics181st.markdown("### Key Metrics", unsafe_allow_html=True)182col1, col2, col3 = st.columns(3)183col1.metric(184    label="Total Incidents",185    value=f"{len(filtered):,}"186)187col2.metric(188    label="Unique Crime Types",189    value=f"{filtered['crm_cd_desc'].nunique():,}"190)191# compute share of the top crime192top_share = top_crimes.iloc[0]["Percentage"]  193col3.metric(194    label=f"Share of Top Crime ({top_crimes.iloc[0]['Crime Type']})",195    value=f"{top_share:.1%}"196)197 198 199# -------------------------------- Plot 1: Pie(Donut) Chart --------------------------------200fig = px.pie(201    top_crimes,202    names="Crime Type",203    values="Count",204    hole=0.4,205    color_discrete_sequence=px.colors.sequential.Agsunset,206    title="  "207)208    209fig.update_traces(210    textposition="outside",211    textinfo="label+percent",212    pull=[0.02] * len(top_crimes),213    marker=dict(line=dict(color="white", width=1))214)215    216fig.update_layout(217    legend_title_text="Crime Type",218    margin=dict(t=40, b=40, l=20, r=20),219    height=600,220    width=450,221    title_x=0.5222)223    224# Display the plot.225st.plotly_chart(fig, use_container_width=True)226    227# Description.228st.markdown("""<div class="description"> The donut chart elegantly shows how ten key crime categories divide the incidents for the selected year into distinct slices. 229Circular rings highlight property crimes especially vehicle theft as the most common offenses, while smaller wedges represent less frequent incidents such as vandalism, 230criminal threats and minor burglary. Violent acts such as simple assault and robbery occupy medium sized segments, creating a clear visual hierarchy of frequency without 231relying on specific numbers. By pairing each slice with its label, the chart provides an immediate intuitive understanding of which crime types contribute most to overall 232volume and which are comparatively rare, helping stakeholders focus on the offenses that matter most.</div>""",unsafe_allow_html=True)233 234# -------------------------------- Plot : Bubble Map of Incident Counts by Region --------------------------------235# st.markdown("<div class='sectionheader'>Crime Hotspots by Region</div>", unsafe_allow_html=True)236 237# # 1. Aggregate counts and centroids238# region_stats = (239#     df240#       .groupby("RegionName")241#       .agg(242#           Count   = pd.NamedAgg(column="crm_cd_desc", aggfunc="size"),243#           Latitude  = pd.NamedAgg(column="lat", aggfunc="mean"),244#           Longitude = pd.NamedAgg(column="lon", aggfunc="mean")245#       )246#       .reset_index()247# )248 249# # 2. Build the bubble map250# fig = px.scatter_mapbox(251#     region_stats,252#     lat="Latitude",253#     lon="Longitude",254#     size="Count",                       # bubble size ~ incident volume255#     color="Count",                      # color gradient for emphasis256#     hover_name="RegionName",257#     hover_data={"Count":True, "Latitude":False, "Longitude":False},258#     size_max=30,                        # max bubble diameter259#     zoom=10,                            # adjust to focus your city260#     mapbox_style="open-street-map",261#     title="Crime Volume by Region (Bubble Map)"262# )263 264# # 3. Tidy layout265# fig.update_layout(266#     margin=dict(t=50, b=0, l=0, r=0),267#     legend_title_text="Incident Count",268#     title_x=0.5269# )270 271# # 4. Render272# st.plotly_chart(fig, use_container_width=True)273 274# -------------------------------- Plot 2: Stacked Bar Charts for Regions --------------------------------275st.markdown("<div class='sectionheader'>Crime Composition by Region: Top 5 Offenses </div>", unsafe_allow_html=True)276# 1. Compute counts per region and crime277counts = (278    df279    .groupby(['RegionName', 'crm_cd_desc'])280    .size()281    .reset_index(name='Count')282)283 284# 2. For each region, keep only its top 5 crime types285top5_per_region = (286    counts287    .groupby('RegionName', group_keys=False)288    .apply(lambda grp: grp.nlargest(5, 'Count'))289)290 291# 3. Draw a stacked bar chart292fig = px.bar(293    top5_per_region,294    x='RegionName',295    y='Count',296    color='crm_cd_desc',297    color_discrete_sequence=px.colors.sequential.Agsunset,298    title='Top 5 Crimes by Region',299    labels={'crm_cd_desc': 'Crime Type'},300    height=600301)302 303# 4. Tweak layout for readability304fig.update_layout(305    barmode='stack', 306    xaxis_tickangle=-45,307    xaxis_title='', 308    yaxis_title='Incident Count',309    legend_title_text='Crime Type',310    margin=dict(t=50, b=150, l=50, r=50)311)312 313# 5. Render in Streamlit314st.plotly_chart(fig, use_container_width=True)315# Description.316st.markdown("""<div class="description"> This stacked‐bar chart breaks down each region’s crime profile by its five most common offenses. The bars’ 317layers show how certain neighborhoods are dominated by property crimes (like vehicle theft and petty theft), whereas others carry a heavier share of 318violent or specialty offenses. By grouping all five slices together, the visualization highlights both the volume and mix of crimes in each area—revealing, 319for example, precincts where assault plays a disproportionately large role versus those driven mainly by theft. This makes it straightforward to compare how 320offense patterns differ from one region to the next.</div>""",unsafe_allow_html=True)321 322# -------------------------------- Plot 3: Line Chart for Incident Counts by Region --------------------------------323st.markdown("<div class='sectionheader'>Incidents Trends over Time </div>", unsafe_allow_html=True)324# 1. Aggregate total incidents by year325yearly_region = (326    df327      .groupby(["year", "RegionName"])328      .size()329      .reset_index(name="Count")330)331 332# 2. Let the user pick one region to highlight333regions = sorted(yearly_region["RegionName"].unique())334sel_region = st.selectbox("Select Region", ["All"] + regions, index=0)335 336if sel_region != "All":337    yearly_region = yearly_region[yearly_region["RegionName"] == sel_region]338 339# 3. Plot a smooth line per region (or just the one selected)340fig = px.line(341    yearly_region,342    x="year",343    y="Count",344    color="RegionName",345    title=(" "),346    labels={"year":"Year", "Count":"Incident Count"}347)348 349# 4. Add LOWESS smoothing (optional)350for trace in fig.data:351    trace.update(mode="lines")  # remove markers352 353st.plotly_chart(fig, use_container_width=True)354# Description.355st.markdown("""<div class="description"> This multi‐line chart tracks how total crime incidents have evolved across LAPD regions from 2020 356through 2025. Each colored line represents a different precinct, letting you compare their trajectories side by side. You’ll notice that most 357areas rose to a peak around 2022 before tapering off, while a handful of regions bucked the trend—either holding steady or dipping earlier. The 358clear visual of converging and diverging lines makes it easy to spot which precincts saw the sharpest upticks, which managed to keep incidents 359relatively flat, and how the overall pattern shifted over the five‐year span.</div>""",unsafe_allow_html=True)360 361 362 363# -------------------------------- Plot : Bubble Map of Incident Counts by Region NO MAP --------------------------------364# st.markdown("<div class='sectionheader'>Crime Hotspots by Region NO MAP</div>", unsafe_allow_html=True)365 366# # 1. Aggregate total incidents by region and pick top 10367# region_counts = (368#     df369#       .groupby("RegionName")            # group by your text field370#       .size()                           # count rows371#       .reset_index(name="Count")        # turn it into a DataFrame with columns RegionName & Count372# )373# top_regions = region_counts.head(10)374# # 2. Build the bubble chart375# fig = px.scatter(376#     top_regions,377#     x='Count',378#     y='RegionName',379#     size='Count',               # bubble area ∝ incident count380#     color='Count',              # color scale also shows volume381#     hover_name='RegionName',        # show region on hover382#     hover_data={'Count':True},383#     size_max=60,                # max bubble diameter384#     title='Top 10 Regions by Crime Volume (Bubble Chart)'385# )386 387# # 3. Tweak layout388# fig.update_layout(389#     xaxis_tickangle=-45,        # tilt x-labels so they’re legible390#     margin=dict(t=50, b=100),391#     yaxis_title='Incident Count',392#     xaxis_title=''393# )394 395# # 4. Render in Streamlit396# st.plotly_chart(fig, use_container_width=True)397 398# -------------------------------- Plot 4: Heat Map --------------------------------399st.markdown("<div class='sectionheader'> HeatMap </div>", unsafe_allow_html=True)400# Count the crime type and list out the top 10 crime type that have the most cases.401top_crimes = df['crm_cd_desc'].value_counts().nlargest(10).index402df_top = df[df['crm_cd_desc'].isin(top_crimes)]403    404# Group by crime type and year.405heatmap1_data = df_top.groupby(['crm_cd_desc', 'year']).size().unstack(fill_value=0)406    407# Create the heat map.408fig, ax = plt.subplots(figsize=(8, 4))409    410# 2. Draw into that Axes411sns.heatmap(412    heatmap1_data,413    annot=True,414    fmt="d",415    cmap="YlOrRd",416    ax=ax,417    annot_kws={"size": 6},               # smaller numbers in cells418    cbar_kws={"shrink": 0.5}             # shrink the colorbar419)420    421# 3. Set titles/labels with a smaller font422ax.set_title("Top 10 Crime Types by Year", fontsize=10, pad=8)423ax.set_xlabel("Year", fontsize=8, labelpad=6)424ax.set_ylabel("Crime Type", fontsize=8, labelpad=6)425 426# Shrink the tick labels427ax.tick_params(axis='x', labelsize=10, rotation=0)  # no rotation, smaller font428ax.tick_params(axis='y', labelsize=10)              # smaller font429    430# 4. Tight layout431fig.tight_layout()432    433# 5. Render in Streamlit434st.pyplot(fig)435    436# Description.437st.markdown("""<div class="description">438This heatmap shows the frequency of the top 10 crimes from 2020 to 2025. The x axis is year and the y axis is crime type. The colormap is 'YlOrRd' to create a distinct visual difference in number of incidents. Dark red means that the incident frequency is high while light yellow means that the incident frequency is low. 'Vehicle Stolen' seems to be the most prevalent crime for all five years, given its values are highlighted in deeper shades of red. 'Vehicle Stolen' also seems to fluctuate between 20000 and 24000 throughout the five years. 'Thief of identity' also saw a spike in incident frequency for 2022, recording 21251 crimes. Limiting the heatmap to top 10 crimes addressed the most prominent crimes in LA. Since 2025 is not over, data for that year is still relatively inclusive. This visualization can help law enforcement easily detect trends of different crimes for a specific year. This data may allow them to predict future rates and be able to allocate resources accordingly to mitigate these crimes.439</div>""",unsafe_allow_html=True)440 441 442# -------------------------------- Plot 5: Line Chart --------------------------------443st.markdown("<div class='sectionheader'> Line Chart </div>", unsafe_allow_html=True)444# Filter out the year 2025 since it is not the end, so that the trend can't be see.445df = df[df['year'] != 2025]446    447# Group the each crime type by year.448yearly_crime_counts = (449    df.groupby(["year", "crm_cd_desc"])450    .size()451    .reset_index(name="Count")452)453    454# Filter the crime types that have the most top 5 cases.455top5_crimes = df["crm_cd_desc"].value_counts().nlargest(5).index456filtered_crimes = yearly_crime_counts[yearly_crime_counts["crm_cd_desc"].isin(top5_crimes)]457    458# Plot the line plot.459line_chart = alt.Chart(filtered_crimes).mark_line(point=True).encode(460    x=alt.X("year:O", title="Year"),461    y=alt.Y("Count:Q", title="Number of Incidents"),462    color=alt.Color("crm_cd_desc:N", title="Crime Type"),463    tooltip=["year", "crm_cd_desc", "Count"]464).properties(465    title="Yearly Trends of Top 5 Crime Types",466    width=700,  467    height=400468)469    470# Display the plot.471line_chart472    473# Description.474st.markdown("""<div class="description">475This plot is a line chart visualizing the annual number of incidents for the top 5 most frequent crime types over a five-year period, from 2020 to 2024. 476Each line represents a distinct crime type, allowing for easy comparison of trends across different categories. 477The x-axis represents the year, the y-axis indicates the number of incidents, and a legend identifies the color corresponding to each specific 478crime type: Battery - Simple Assault, Burglary From Vehicle, Theft of Identity, Vandalism - Felony , and Vehicle - Stolen. The plot highlights 479the fluctuations and overall trajectories of these major crime categories across the years.</div>""",unsafe_allow_html=True)480 481 482# -------------------------------- Plot 6: Map --------------------------------483st.markdown("<div class='sectionheader'> Explore LA Crime Patterns: An Interactive Folium Map </div>", unsafe_allow_html=True)484# Load the data.485with open(GEOJSON_PATH, "r", encoding="utf-8") as f:486    geojson_data = json.load(f)487        488# Identify top 10 crime types489top_10_crimes = df['crm_cd_desc'].value_counts().nlargest(10).index.tolist()490    491# Filter the main DataFrame to include only top 10 crimes492df_top = df[df['crm_cd_desc'].isin(top_10_crimes)]493    494# Creat dropdown menu495years = sorted(df['year'].unique())496year_dropdown = st.selectbox("Year: ", years)497crime_dropdown = st.selectbox("Crime Type: ", top_10_crimes)498    499# Filter data.500df_filtered = df[(df['year'] == year_dropdown) & (df['crm_cd_desc'] == crime_dropdown)].sample(n=300, random_state=1)501    502# Create the new folium map to make the map more interactive.503# Method comes from: https://folium.streamlit.app/.504new_map = folium.Map(location=[df_filtered['lat'].mean(), df_filtered['lon'].mean()], zoom_start=10)505    506# Add county boundary507folium.GeoJson(geojson_data, name="County Boundaries").add_to(new_map)508 509# # Create the map.510# def crime_map(year, crime):511#     df_filtered = df[(df['year'] == year) & (df['crm_cd_desc'] == crime)].sample(n=300, random_state=1)512#     gdf_points = gpd.GeoDataFrame(513#         df_filtered,514#         geometry=gpd.points_from_xy(df_filtered['lon'], df_filtered['lat']),515#         crs="EPSG:4326"516#     )517    518#     fig, ax = plt.subplots(figsize=(10, 10))519#     gdf_counties.plot(ax=ax, color='lightgray', edgecolor='white')520#     gdf_points.plot(ax=ax, color='red', markersize=10, alpha=0.6)521#     ax.set_title(f"{crime} - {year}")522#     ax.set_xlabel("Longitude")523#     ax.set_ylabel("Latitude")524#     plt.grid(True)525#     st.pyplot(fig)526 527# # Call the function with selected values528# crime_map(year_dropdown, crime_dropdown)529 530# Using for-loop to add the crime points531for _, row in df_filtered.iterrows():532    folium.CircleMarker(533        location=[row['lat'], row['lon']],534        radius=3,535        color='red',536        fill=True,537        fill_opacity=0.6,538        popup=row['crm_cd_desc']539    ).add_to(new_map)540    541# Display the new map.542st_folium(new_map, width=1000, height=500, use_container_width=True)543    544# Description.545st.markdown("""<div class="description">546This visualization uses Folium to build an interactive map of crime distribution in Los Angeles, highlighting the geospatial clustering characteristics of different years and crime types, and emphasizing the user's experience of freely exploring the map. The base map uses real streets and geographic backgrounds to enhance the spatial visualization of the image. The map shows the administrative boundaries of Los Angeles County in blue polygons, which are loaded with GeoJSON data and overlaid on the map to specify the geographic boundaries of crime locations. The red dots on the map represent the location of individual crimes, and the system samples no more than 300 data items from this category for visualization, with each dot pinpointed by latitude and longitude coordinates. The map supports full Leaflet.js functionality, including zooming, dragging, layer control, and other operations, which greatly enhances the flexibility of data exploration. A drop-down menu in the upper left corner of the page allows users to customize filters for specific years and crime types, enabling instant updates to the map content.547</div>""",unsafe_allow_html=True)548 549# -------------------------------- Plot 7: Stacked Bar Chart --------------------------------550st.markdown("<div class='sectionheader'>Trends in Top 10 Crime Types (2020–2024)</div>", unsafe_allow_html=True)551# Group by crime type and year.552stacked_year_df = df_top.groupby(['year', 'crm_cd_desc']).size().reset_index(name='count')553    554# Create the stacked bar chart.555bar_chart = alt.Chart(stacked_year_df).mark_bar().encode(556    x=alt.X('year:O', title='Year'),557    y=alt.Y('count:Q', stack='zero', title='Number of Incidents'),558    color=alt.Color('crm_cd_desc:N', title='Crime Type'),559    tooltip=['year', 'crm_cd_desc', 'count']560).properties(561    width=600,562    height=400,563    title='Stacked Crime Composition by Year (Top 10 Crime Types)'564)565    566# Display the plot.567st.altair_chart(bar_chart, use_container_width=True)568    569# Description.570st.markdown("""<div class="description">571Description: Our stacked bar chart shows the number of reported crimes for the top 10 most common crime types from 2020 to 2024. Each bar represents a year, and the different colors in the bars show different types of crimes, like stolen vehicles, burglary, vandalism, and assault. The taller the colored section, the more incidents of that crime there were in that year.572    573By observing the plot, we can find out that 2022 had the most crimes, the year had the second most crimes is 2023, and etc. Besides that, we can also find out that some crimes, like vehicle theft, petty theft, and burglary from vehicles, happened a lot every year and make up a big part of the total.574</div>""",unsafe_allow_html=True)575 576# -------------------------------- Plot 8: Bar Chart --------------------------------577st.markdown("<div class='sectionheader'>Crime Rankings for Selected Year</div>", unsafe_allow_html=True)578# Group by crime type and year.579heatmap1_df = df_top.groupby(['crm_cd_desc', 'year']).size().reset_index(name='count')580    581# Create the slider based on the previous heatmap.582year_slider = alt.binding_range(min=heatmap1_df['year'].min(), max=heatmap1_df['year'].max(), step=1)583year_select = alt.selection_point(fields=['year'], bind=year_slider, value = 2022, name="Select")584    585# Convert the heatmap into bar chart.586barchart = alt.Chart(heatmap1_df).mark_bar().encode(587    x=alt.X('crm_cd_desc:N', title='Crime Type', sort='-y'),588    y=alt.Y('count:Q', title='Number of Incidents'),589    color=alt.Color('crm_cd_desc:N', title='Crime Type'),590    tooltip=['crm_cd_desc', 'count']591).transform_filter(592    year_select593).add_params(594    year_select595).properties(596    width=600,597    height=400,598    title='Top 10 Crime Types (Filtered by Year)'599)600    601# Display the plot.602barchart603    604# Description.605st.markdown("""<div class="description"> This interactive bar chart allows users to explore the most frequently reported crime types in Los Angeles by year. By adjusting the slider below the chart, the visualization updates in real time to show the top ten crime categories for the selected year. Each bar represents the total number of incidents, with color coding used to distinguish different crime types and a legend on the right for reference.606This visualization makes it easy to compare how the composition of major crime types evolves over time and to detect emerging issues that may require further investigation or policy response.607</div>""",unsafe_allow_html=True)608 609st.markdown("<div class='title'><h4>Reference: LAPD Crime Data</h4></div>", unsafe_allow_html=True)