File size: 6,657 Bytes
6cfa539 54fa991 ac01220 acb177a 54fa991 167e956 840e1cf 19f42ee 840e1cf 19f42ee b434a0b 19f42ee 2c20fd0 cf1321a f00a29c cf1321a a5e7021 840e1cf 188b003 167e956 54fa991 eb30515 19f42ee eb30515 167e956 54fa991 0c1ff68 d49e66a bc4ed7b 54fa991 a174729 4f2b40c 2296734 54fa991 4f2b40c 2296734 ea6bf86 f00a29c ea6bf86 3d0b2c0 ea6bf86 4f2b40c 54fa991 2296734 47ccdf0 4f2b40c 47ccdf0 54fa991 9e667cc f00a29c 54fa991 6cfa539 acb177a 9e667cc 784e8fe 92c4e53 784e8fe c55a4cb acb177a 5a67e5c 683f503 784e8fe 76e95ba 683f503 acb177a 9f7e69b 9dffac3 92c4e53 4f7fa58 9dffac3 4f7fa58 3d6e51e a20d9a1 3d6e51e acb177a e6da3fa 9f7e69b 9dffac3 c55a4cb 24ecf2d 9f7e69b 9dffac3 950c721 4f7fa58 9f7e69b 9dffac3 9e667cc 950c721 9dffac3 3397ec7 167e956 4d0e424 4837224 | 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 | import streamlit as st
import pandas as pd
import altair as alt
from pathlib import Path
import plotly.express as px
# ── 0. Page configuration ──
st.set_page_config(
page_title="Analyze Crime Distributions",
page_icon="📊",
layout="wide"
)
st.markdown("""
<style>
.title {
text-align: center;
padding: 25px;
color: #2c3e50;
font-family: 'Source Sans Pro', sans-serif;
}
/* Paragraph/write-up styling */
.description {
font-size: 18px; /* comfortable reading size */
line-height: 1.6; /* good spacing */
color: #4b4b4b; /* dark grey text */
text-align: justify; /* nice full-justified look */
padding: 0 10px 20px; /* side & bottom padding */
font-family: 'Helvetica Neue', Arial, sans-serif;
}
.sectionheader {
font-family: 'Source Sans Pro', sans-serif;
font-size: 32px;
color: #2c3e50;
margin-top: 15px;
margin-bottom: 10px;
border-bottom: 3px solid #ccc;
padding-bottom: 8px;
}
</style>
""", unsafe_allow_html=True)
st.markdown("<div class='title'><h1> LAPD Crime Insights Dashboard </h1></div>", unsafe_allow_html=True)
# 1. Page title
st.markdown("""<div class='description'> This application provides a suite of interactive visualizations—pie charts,
bar charts, scatter plots, and more—that let you explore crime patterns in the LAPD dataset from multiple angles.
Quickly see which offense categories dominate, compare arrest rates against non-arrests, track how crime volumes change over time, and examine geographic hotspots.
These insights can help police departments, community organizations, and policymakers allocate resources more effectively and
design targeted strategies to improve public safety.</div>""",unsafe_allow_html=True)
# 2. Data info & load
st.markdown("<div class='sectionheader'> Dataset Information </div>", unsafe_allow_html=True)
st.markdown(
"""
<div class="description">
<ul>
<li><strong>Source:</strong> LAPD crime incidents dataset</li>
<li><strong>Rows:</strong> one incident per row</li>
<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>
<li><strong>Purpose:</strong> Interactive exploration of top crime categories and arrest rates.</li>
</ul>
</div>
""",
unsafe_allow_html=True
)
# 1. Resolve the path to the CSV next to this script
DATA_PATH = Path(__file__).parent / "crime_data.csv" # /app/src/crime_data.csv
REGION_DATA_PATH = Path(__file__).parent / "area_lookup.csv" # /app/src/crime_data.csv
@st.cache_data
def load_data():
return pd.read_csv(DATA_PATH)
def region_load_data():
return pd.read_csv(REGION_DATA_PATH)
if st.button("🔄 Refresh Data"):
st.cache_data.clear() # Clear the cache
st.toast("Data is refreshed",icon="✅") # Reload the data
# 2. Load and early‐exit if missing
df = load_data()
lookup = region_load_data()
map_region = dict(zip(lookup["OBJECTID"], lookup["APREC"]))
map_precinct = dict(zip(lookup["OBJECTID"], lookup["PREC"]))
# Map into new columns
df["RegionName"] = df["area"].map(map_region)
df["PrecinctCode"] = df["area"].map(map_precinct)
# Inspect
print(df[["area", "RegionName", "PrecinctCode"]].head())
if df.empty:
st.stop()
# 3. Data preview
st.markdown("<div class='sectionheader'> Data Preview </div>", unsafe_allow_html=True)
st.markdown(
f"<div class='description'>"
f"Total records: <strong>{df.shape[0]:,}</strong> | "
f"Total columns: <strong>{df.shape[1]:,}</strong>"
f"</div>",
unsafe_allow_html=True
)
st.dataframe(df.head())
# Pie Chart 1: Top 10 Crime Types
st.markdown("<div class='sectionheader'> Top 10 Crime Types by Year </div>", unsafe_allow_html=True)
years = sorted(df["year"].dropna().astype(int).unique())
# Prepend an “All” option
options = ["All"] + years
# Year filter (shorter, above chart)
selected_year = st.selectbox("Select Year", options, index=0)
# col_empty, col_filter = st.columns([3,1])
# with col_filter:
# selected_year = st.selectbox(
# "Select Year",
# options=options,
# index=0, # default to “All”
# key="year_filter"
# )
# Filter according to selection
if selected_year == "All":
filtered = df.copy()
else:
filtered = df[df["year"] == selected_year]
# Compute top 10 crime types for that year ──
top_crimes = (
filtered["crm_cd_desc"]
.value_counts()
.nlargest(10)
.rename_axis("Crime Type")
.reset_index(name="Count")
)
top_crimes["Percentage"] = top_crimes["Count"] / top_crimes["Count"].sum()
#Key Metrics
st.markdown("### Key Metrics", unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
col1.metric(
label="Total Incidents",
value=f"{len(filtered):,}"
)
col2.metric(
label="Unique Crime Types",
value=f"{filtered['crm_cd_desc'].nunique():,}"
)
# compute share of the top crime
top_share = top_crimes.iloc[0]["Percentage"]
col3.metric(
label=f"Share of Top Crime ({top_crimes.iloc[0]['Crime Type']})",
value=f"{top_share:.1%}"
)
# Plotly donut chart ──
fig = px.pie(
top_crimes,
names="Crime Type",
values="Count",
hole=0.4,
color_discrete_sequence=px.colors.sequential.Agsunset,
title=" "
)
fig.update_traces(
textposition="outside",
textinfo="label+percent",
pull=[0.02] * len(top_crimes),
marker=dict(line=dict(color="white", width=1))
)
fig.update_layout(
legend_title_text="Crime Type",
margin=dict(t=40, b=40, l=20, r=20),
height=600,
width=450,
title_x=0.5
)
st.plotly_chart(fig, use_container_width=True)
st.markdown("""<div class="description"> The donut chart shows the share of the ten most frequent crime categories in the selected year.
At the center, you can see that Vehicle – Stolen is the single largest slice, accounting for roughly 18.7% of all incidents,
The remaining five categories each represent between 3%–5% of total incidents—these include miscellaneous crimes, criminal threats,
assault with a deadly weapon, burglary, and minor vandalism. By displaying both slice size and percentage labels, the chart makes it easy
to compare how dominant property‐related offenses are, versus violent or lesser‐common crimes, in that year’s LAPD data.</div>""",unsafe_allow_html=True)
|