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> &nbsp;|&nbsp; "
    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)