crime / app.py
venkatl's picture
Update app.py
3a7ff6f verified
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import gradio as gr
from io import BytesIO
import base64
import random
import scipy.stats as ss
from PIL import Image
from cite import citation
def fig_to_pil(fig):
buf = BytesIO()
fig.savefig(buf, format="png", bbox_inches="tight")
buf.seek(0)
return Image.open(buf)
def cramers_v(confusion_matrix):
""" Cramér's V for categorical correlation """
chi2 = ss.chi2_contingency(confusion_matrix)[0]
n = confusion_matrix.sum().sum()
r, k = confusion_matrix.shape
return np.sqrt(chi2 / (n * (min(r, k) - 1)))
def create_analysis_interface(df):
"""
Creates the Gradio interface components for a given dataframe.
This function returns a list of Tabs or renders them into the current context.
"""
numeric_cols = df.select_dtypes(include=['number']).columns
categorical_cols = df.select_dtypes(include=['object', 'category']).columns
# ----------------------------------------------------
# 1. Descriptive Statistics
# ----------------------------------------------------
def get_descriptive_stats():
stats = df[numeric_cols].describe().T
stats = stats.reset_index().rename(columns={"index": "Feature"})
return stats
def download_keyword_counts(df_in):
r = random.randint(1,1000)
path = f"keyword_counts_{r}.csv"
df_in.to_csv(path, index=False)
return path
# ----------------------------------------------------
# 2. Keyword Frequency Table + Plots
# ----------------------------------------------------
def keyword_frequency(column):
series = df[column].dropna().astype(str).str.split(',').explode().str.strip()
counts = series.value_counts().reset_index()
counts.columns = ["Keyword", "Count"]
total = counts["Count"].sum()
# Percentage as numeric (rounded to 1 decimal place)
counts["Percentage"] = (counts["Count"] / total * 100).round(1)
# --- BAR CHART (matplotlib → PIL) ---
fig_bar, ax_bar = plt.subplots(figsize=(8,4))
ax_bar.bar(counts["Keyword"].head(15), counts["Count"].head(15))
ax_bar.set_title(f"Top Keywords in {column}")
ax_bar.set_xticklabels(counts["Keyword"].head(15), rotation=45, ha='right')
bar_img = fig_to_pil(fig_bar)
plt.close(fig_bar)
# --- PIE CHART (matplotlib → PIL) ---
fig_pie, ax_pie = plt.subplots(figsize=(6,6))
ax_pie.pie(
counts["Count"].head(10),
labels=counts["Keyword"].head(10),
autopct="%1.1f%%"
)
ax_pie.set_title(f"{column} (Distribution)") #Pie Chart
pie_img = fig_to_pil(fig_pie)
plt.close(fig_pie)
# --- HORIZONTAL BAR CHART ---
fig_hbar, ax_hbar = plt.subplots(figsize=(8,6))
ax_hbar.barh(counts["Keyword"].head(15), counts["Count"].head(15))
ax_hbar.set_title(f"Top Keywords in {column} (Horizontal Bar)")
plt.tight_layout()
hbar_img = fig_to_pil(fig_hbar)
plt.close(fig_hbar)
# --- PARETO CHART (80/20) ---
counts_sorted = counts.sort_values("Count", ascending=False)
cum_percentage = (counts_sorted["Count"].cumsum() / counts_sorted["Count"].sum()) * 100
fig_pareto, ax1 = plt.subplots(figsize=(8,4))
ax1.bar(counts_sorted["Keyword"].head(15), counts_sorted["Count"].head(15), color='skyblue')
ax2 = ax1.twinx()
ax2.plot(counts_sorted["Keyword"].head(15), cum_percentage.head(15), color='red', marker="o")
ax1.set_xticklabels(counts_sorted["Keyword"].head(15), rotation=45, ha='right')
ax1.set_title(f"Pareto Analysis of {column}")
pareto_img = fig_to_pil(fig_pareto)
plt.close(fig_pareto)
# --- SCATTER PLOT (Rank vs Frequency) ---
counts["Rank"] = range(1, len(counts) + 1)
fig_scatter, ax_scatter = plt.subplots(figsize=(6,4))
ax_scatter.scatter(counts["Rank"], counts["Count"])
ax_scatter.set_title(f"Rank vs Frequency for {column}")
ax_scatter.set_xlabel("Rank (1 = most common)")
ax_scatter.set_ylabel("Frequency")
scatter_img = fig_to_pil(fig_scatter)
plt.close(fig_scatter)
# --- CUMULATIVE DISTRIBUTION PLOT ---
fig_cum, ax_cum = plt.subplots(figsize=(6,4))
ax_cum.plot(cum_percentage.values)
ax_cum.set_title(f"Cumulative Distribution of {column}")
ax_cum.set_ylabel("Cumulative %")
ax_cum.set_xlabel("Keyword Rank")
cum_img = fig_to_pil(fig_cum)
plt.close(fig_cum)
return counts, bar_img, pie_img, hbar_img, pareto_img, scatter_img, cum_img
# ----------------------------------------------------
# 3. Correlation Explorer
# ----------------------------------------------------
def explore_two_columns(col1, col2):
c1 = df[col1]
c2 = df[col2]
images = []
result_text = ""
# NUMERIC vs NUMERIC
if col1 in numeric_cols and col2 in numeric_cols:
# Pearson
corr = c1.corr(c2)
result_text = f"Pearson Correlation = {corr:.4f}"
# Scatter
fig, ax = plt.subplots(figsize=(6,4))
ax.scatter(c1, c2)
ax.set_xlabel(col1)
ax.set_ylabel(col2)
ax.set_title(f"{col1} vs {col2} (Scatter)")
images.append(fig_to_pil(fig))
plt.close(fig)
# Regression
fig, ax = plt.subplots(figsize=(6,4))
sns.regplot(x=c1, y=c2, ax=ax)
ax.set_title("Regression Line")
images.append(fig_to_pil(fig))
plt.close(fig)
# Distributions
fig, ax = plt.subplots(figsize=(6,4))
sns.histplot(c1, color="blue", kde=True, label=col1)
sns.histplot(c2, color="orange", kde=True, label=col2)
ax.legend()
ax.set_title("Distribution Comparison")
images.append(fig_to_pil(fig))
plt.close(fig)
print(result_text)
return result_text, None, images[0], images[1], images[2]
# CATEGORICAL vs CATEGORICAL
if col1 in categorical_cols and col2 in categorical_cols:
confusion = pd.crosstab(c1, c2)
v = cramers_v(confusion)
result_text = f"Cramér’s V = {v:.4f}"
conf = pd.crosstab(c1,c2, margins=True, margins_name="Total")
conf_table = conf.reset_index()
conf_table.columns = ["Category_1"] + list(conf.columns)
# Heatmap
fig, ax = plt.subplots(figsize=(6,4))
sns.heatmap(confusion, cmap="Blues", annot=True, fmt="d")
ax.set_title("Crosstab Heatmap")
images.append(fig_to_pil(fig))
plt.close(fig)
# Bar chart
fig, ax = plt.subplots(figsize=(6,4))
confusion.sum(axis=1).plot(kind='bar', ax=ax)
ax.set_title(f"Correlation between {col1} and {col2}")
images.append(fig_to_pil(fig))
plt.close(fig)
print(result_text)
return result_text, conf_table, images[0], images[1], None
# MIXED TYPES (numeric + categorical)
# Ensure correct assignment
if col1 in categorical_cols and col2 in numeric_cols:
cat = col1; num = col2
else:
cat = col2; num = col1
result_text = f"Numeric vs Categorical Analysis ({num} by {cat})"
# Boxplot
fig, ax = plt.subplots(figsize=(6,4))
sns.boxplot(x=df[cat], y=df[num], ax=ax)
ax.set_title("Boxplot")
plt.xticks(rotation=45, ha='right')
images.append(fig_to_pil(fig))
plt.close(fig)
# Violin plot
fig, ax = plt.subplots(figsize=(6,4))
sns.violinplot(x=df[cat], y=df[num], ax=ax)
ax.set_title("Violin Plot")
plt.xticks(rotation=45, ha='right')
images.append(fig_to_pil(fig))
plt.close(fig)
print(result_text)
return result_text, None, images[0], images[1], None
def explore_two_columns_statistical(col1, col2):
c1 = df[col1]
c2 = df[col2]
images = []
result_text = ""
stat_results = ""
# NUMERIC vs NUMERIC
if col1 in numeric_cols and col2 in numeric_cols:
# Pearson
corr = c1.corr(c2)
result_text = f"Pearson Correlation = {corr:.4f}"
# Spearman
spearman_corr, spearman_p = ss.spearmanr(c1, c2)
stat_results += f"Spearman Correlation: {spearman_corr:.4f} (p={spearman_p:.4e})\n"
# Scatter
fig, ax = plt.subplots(figsize=(6,4))
ax.scatter(c1, c2)
ax.set_xlabel(col1)
ax.set_ylabel(col2)
ax.set_title(f"{col1} vs {col2} (Scatter)")
images.append(fig_to_pil(fig))
plt.close(fig)
# Regression
fig, ax = plt.subplots(figsize=(6,4))
sns.regplot(x=c1, y=c2, ax=ax)
ax.set_title("Regression Line")
images.append(fig_to_pil(fig))
plt.close(fig)
# Distributions
fig, ax = plt.subplots(figsize=(6,4))
sns.histplot(c1, color="blue", kde=True, label=col1)
sns.histplot(c2, color="orange", kde=True, label=col2)
ax.legend()
ax.set_title("Distribution Comparison")
images.append(fig_to_pil(fig))
plt.close(fig)
return result_text, stat_results, None, images[0], images[1], images[2]
# CATEGORICAL vs CATEGORICAL
if col1 in categorical_cols and col2 in categorical_cols:
confusion = pd.crosstab(c1, c2)
v = cramers_v(confusion)
result_text = f"Cramér’s V = {v:.4f}"
# Chi-Square Test
chi2, p, dof, expected = ss.chi2_contingency(confusion)
stat_results += f"Chi-Square Statistic: {chi2:.4f}\n"
stat_results += f"P-value: {p:.4e}\n"
stat_results += f"Degrees of Freedom: {dof}\n"
if p < 0.05:
stat_results += "Result: Significant Association (Reject H0)\n"
else:
stat_results += "Result: No Significant Association (Fail to reject H0)\n"
conf = pd.crosstab(c1,c2, margins=True, margins_name="Total")
conf_table = conf.reset_index()
conf_table.columns = ["Category_1"] + list(conf.columns)
# Heatmap
fig, ax = plt.subplots(figsize=(6,4))
sns.heatmap(confusion, cmap="Blues", annot=True, fmt="d")
ax.set_title("Crosstab Heatmap")
images.append(fig_to_pil(fig))
plt.close(fig)
# Bar chart
fig, ax = plt.subplots(figsize=(6,4))
confusion.sum(axis=1).plot(kind='bar', ax=ax)
ax.set_title(f"Correlation between {col1} and {col2}")
images.append(fig_to_pil(fig))
plt.close(fig)
return result_text, stat_results, conf_table, images[0], images[1], None
# MIXED TYPES (numeric + categorical)
# Ensure correct assignment
if col1 in categorical_cols and col2 in numeric_cols:
cat = col1; num = col2
else:
cat = col2; num = col1
result_text = f"Numeric vs Categorical Analysis ({num} by {cat})"
# Group data
groups = []
group_labels = []
for val in df[cat].unique():
# Drop NaNs for the test
data_group = df[df[cat] == val][num].dropna()
if len(data_group) > 0:
groups.append(data_group)
group_labels.append(val)
# Check number of groups
if len(groups) == 2:
# T-test
t_stat, p_val = ss.ttest_ind(groups[0], groups[1])
stat_results += f"T-test ({group_labels[0]} vs {group_labels[1]}):\n"
stat_results += f"T-statistic: {t_stat:.4f}\n"
stat_results += f"P-value: {p_val:.4e}\n"
if p_val < 0.05:
stat_results += "Result: Significant Difference (Reject H0)\n"
else:
stat_results += "Result: No Significant Difference (Fail to reject H0)\n"
elif len(groups) > 2:
# ANOVA
f_stat, p_val = ss.f_oneway(*groups)
stat_results += f"One-way ANOVA:\n"
stat_results += f"F-statistic: {f_stat:.4f}\n"
stat_results += f"P-value: {p_val:.4e}\n"
if p_val < 0.05:
stat_results += "Result: Significant Difference among groups (Reject H0)\n"
else:
stat_results += "Result: No Significant Difference among groups (Fail to reject H0) p value less than 0.05\n"
else:
stat_results += "Not enough groups for statistical testing.\n"
# Boxplot
fig, ax = plt.subplots(figsize=(6,4))
sns.boxplot(x=df[cat], y=df[num], ax=ax)
ax.set_title("Boxplot")
plt.xticks(rotation=45, ha='right')
images.append(fig_to_pil(fig))
plt.close(fig)
# Violin plot
fig, ax = plt.subplots(figsize=(6,4))
sns.violinplot(x=df[cat], y=df[num], ax=ax)
ax.set_title("Violin Plot")
plt.xticks(rotation=45, ha='right')
images.append(fig_to_pil(fig))
plt.close(fig)
return result_text, stat_results, None, images[0], images[1], None
# ----------------------------------------------------
# Layout
# ----------------------------------------------------
with gr.Tab("ℹ️ About & Citation"):
gr.Markdown(citation)
with gr.Tab("1️⃣ Descriptive Statistics"):
btn_stats = gr.Button("Generate Stats")
stats_out = gr.Dataframe()
btn_stats.click(get_descriptive_stats, outputs=stats_out)
with gr.Tab("2️⃣ Keyword Frequency Explorer"):
col_select = gr.Dropdown(choices=list(categorical_cols), label="Select Column")
freq_table = gr.Dataframe(label="Keyword Counts")
with gr.Row():
bar_plot = gr.Image(label="Bar Chart")
pie_img = gr.Image(label="Pie Chart")
with gr.Row():
hbar_img = gr.Image(label="Horizontal Bar Chart")
pareto_img = gr.Image(label="Pareto Chart")
with gr.Row():
scatter_img = gr.Image(label="Rank vs Frequency Scatter")
cum_img = gr.Image(label="Cumulative Distribution")
download_btn = gr.Button("Download as CSV")
download_file = gr.File(label="Download File")
col_select.change(keyword_frequency,
inputs=col_select,
outputs=[freq_table, bar_plot, pie_img,hbar_img, pareto_img, scatter_img, cum_img])
download_btn.click(
download_keyword_counts,
inputs=freq_table,
outputs=download_file
)
with gr.Tab("4️⃣ Two-Column Relationship Explorer"):
with gr.Row():
colA = gr.Dropdown(choices=df.columns.tolist(), label="Column A")
colB = gr.Dropdown(choices=df.columns.tolist(), label="Column B")
btn_rel = gr.Button("Explore Relationship")
rel_text = gr.Textbox(label="Summary")
rel_table = gr.Dataframe(label="Crosstab (if categorical)")
with gr.Row():
rel_img1 = gr.Image()
rel_img2 = gr.Image()
rel_img3 = gr.Image()
btn_rel.click(
explore_two_columns,
inputs=[colA, colB],
outputs=[rel_text, rel_table, rel_img1, rel_img2, rel_img3]
)
with gr.Tab("5️⃣ Advanced Statistical Analysis"):
gr.Markdown("### Statistical Tests: T-Test, ANOVA, Chi-Square")
with gr.Row():
colA_stat = gr.Dropdown(choices=df.columns.tolist(), label="Column A")
colB_stat = gr.Dropdown(choices=df.columns.tolist(), label="Column B")
btn_stat = gr.Button("Run Statistical Analysis")
stat_summary = gr.Textbox(label="Summary")
stat_details = gr.Textbox(label="Detailed Statistical Results", lines=10)
stat_table = gr.Dataframe(label="Crosstab (if categorical)")
with gr.Row():
stat_img1 = gr.Image()
stat_img2 = gr.Image()
stat_img3 = gr.Image()
btn_stat.click(
explore_two_columns_statistical,
inputs=[colA_stat, colB_stat],
outputs=[stat_summary, stat_details, stat_table, stat_img1, stat_img2, stat_img3]
)
# ----------------------------------------------------
# Main App Construction
# ----------------------------------------------------
# Load Data
kadapa = 'Kadapa Special Prison for Women'
rajahmundry = 'Rajahmundry special prison for women'
# df_all = pd.read_excel("https://docs.google.com/spreadsheets/d/190HFE1HCdiFjeSyG45rpsg6DHxvtmt7swATedv2mXwY/export?format=xlsx",sheet_name='FinalSheet')
df_all = pd.read_csv("data.csv")
df_kadapa = df_all[df_all['Prison_Name'] == kadapa]
df_rajahmundry = df_all[df_all['Prison_Name'] == rajahmundry]
with gr.Blocks(title="DATA ANALYSIS APP") as app:
gr.Markdown("# 📊 Criminology Data Analysis System \nUpload → Analyse → Export\n Developed by Neeraja")
with gr.Tabs():
with gr.Tab("🌍 All Prisons Data"):
create_analysis_interface(df_all)
with gr.Tab("🏢 Kadapa Prison"):
create_analysis_interface(df_kadapa)
with gr.Tab("🏢 Rajahmundry Prison"):
create_analysis_interface(df_rajahmundry)
app.launch(share=False)