Denver est la ville la plus peuplée du Colorado, un état qui a été récemment classé comme le 21ème plus dangereux des Etats-Unis. C'est aussi l'un des premiers états à avoir légalisé la Marijuana à usage récréationnel en 2014.
D'après les statistiques du FBI, Denver présente un taux de criminalité au-dessus de la moyenne en matière de crimes contre la propriété par exemple les incendies volontaires, les cambriolages, vols, viols etc. Au contraire, lorsque l'on considère les meurtres et braquages le taux est en dessous de la moyenne. Nous allons donc nous intéresser à la problématique suivante :
Quels sont les facteurs socio-économiques par quartier pouvant expliquer un taux supérieur des crimes contre la propriété par rapport à la moyenne nationale ?
Le gouvernement de Denver met à disposition les données concernant les différents crimes recensés dans la ville durant les 5 dernières années. Les données sont issues du système national de déclaration des incidents (NIBRS) et sont ainsi très complètes, incluant tout type de crimes tels que les vols, les viols, les meurtres ou encore le trafic de stupéfiants par exemple. On dispose également des dates précises ainsi que des coordonnées géographiques associées à chaque incident reporté. Ces informations sont précieuses pour étudier l'évolution du taux de criminalité au fil des années, ainsi que pour identifier les quartiers à risques.
Les données disponibles sur le site concernent les délits entre 2015 et 2020, mais on a également pu récupérer un fichier similaire pour les années 2012 à 2017. On va donc mettre en commun les deux fichiers de données.
Source : https://www.denvergov.org/opendata/dataset/city-and-county-of-denver-crime
from IPython.display import HTML
HTML('''<script>
code_show=true;
function code_toggle() {
if (code_show){
$('div.input').hide();
} else {
$('div.input').show();
}
code_show = !code_show
}
$( document ).ready(code_toggle);
</script>
<form action="javascript:code_toggle()"><input type="submit" value="Montrer/cacher le code."></form>''')
import warnings
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.stattools import kpss
from folium import plugins
from folium.plugins import HeatMap
import folium
from IPython.display import IFrame
from sklearn.decomposition import PCA
from sklearn import preprocessing
from plotly.offline import init_notebook_mode; init_notebook_mode()
warnings.simplefilter('ignore')
data_dir = "data/"
df_old = pd.read_csv(data_dir + 'crime_2012_2017.csv')
df_new = pd.read_csv(data_dir + 'crime_2015_2020.csv')
rows_before2015 = np.where(pd.to_datetime(df_old.FIRST_OCCURRENCE_DATE).dt.year <= 2014)[0]
df_old_before2015 = df_old.loc[rows_before2015, :]
df_old_before2015.index = range(df_new.shape[0],
df_new.shape[0] + df_old_before2015.shape[0])
df = pd.concat([df_new, df_old_before2015])
print(df.shape)
df.head()
On dispose de 19 variables dont :
et pas moins de 675052 incidents reportés pour les année 2012 à 2020.
On a effectué un prétraitement des données en rajoutant des colonnes de type temporel afin de nous aider dans la suite de notre étude.
df.FIRST_OCCURRENCE_DATE = pd.to_datetime(df.FIRST_OCCURRENCE_DATE)
df["YEAR"] = df.FIRST_OCCURRENCE_DATE.dt.year
df["DAY"] = df.FIRST_OCCURRENCE_DATE.dt.day
df["DAY_OF_WEEK"] = df.FIRST_OCCURRENCE_DATE.dt.dayofweek
df["MONTH"] = df.FIRST_OCCURRENCE_DATE.dt.month
df["HOUR"] = df.FIRST_OCCURRENCE_DATE.dt.hour
df.index = pd.DatetimeIndex(df["FIRST_OCCURRENCE_DATE"])
df[["YEAR", "DAY", "DAY_OF_WEEK", "MONTH", "HOUR"]].head()
Nous allons séparer les types de délits en 3 classes distinctes :
les crimes violents regroupant les meurtres, braquages et agressions aggravées,
les crimes contre la propriété regroupant les incendies volontaires, larcins, cambriolages, vols de voitures ainsi que les vols dans un véhicule à moteur,
les autres crimes constitués de tous les autres crimes, drogue et alcool, désordre publique, la criminalité en col blanc ainsi que les autres crimes contre les personnes.
df["Violent Crime"] = (df["OFFENSE_CATEGORY_ID"]
=="murder") | (df["OFFENSE_CATEGORY_ID"]
=="robbery") | (df["OFFENSE_CATEGORY_ID"]
=="aggravated-assault")
df["Violent Crime"] = df["Violent Crime"].astype("int32")
df["Property Crime"] = (df["OFFENSE_CATEGORY_ID"]=="arson") | (df["OFFENSE_CATEGORY_ID"]
=="larceny") | (df["OFFENSE_CATEGORY_ID"]
=="burglary") | (df["OFFENSE_CATEGORY_ID"]
=="auto-theft") | (df["OFFENSE_CATEGORY_ID"]
=="theft-from-motor-vehicle")
df["Property Crime"] = df["Property Crime"].astype("int32")
df["Other Crimes"] = (df["OFFENSE_CATEGORY_ID"]=="all-other-crimes") | (df["OFFENSE_CATEGORY_ID"]
=="drug-alcohol") | (df["OFFENSE_CATEGORY_ID"]
=="public-diorder") | (df["OFFENSE_CATEGORY_ID"]
=="white-collar-crime") | (df["OFFENSE_CATEGORY_ID"]
=="other-crimes-against-persons")
df["Other Crimes"] = df["Other Crimes"].astype("int32")
Afin de répondre à la problématique, on va croiser ce jeu de données sur les délits avec un nouveau jeu de données qui décrit le type de population en fonction du quartier d'habitation à Denver. Ce jeu de données comme le précédent est fourni par la ville de Denver (source : https://www.denvergov.org/opendata/dataset/city-and-county-of-denver-american-community-survey-nbrhd-2013-2017).
On a notamment des variables concernant la race, les revenus ou encore le niveau d'études des habitants, les chiffres sont une estimation moyenne sur les années 2013 à 2017.
Les définitions des différentes variables peuvent être lues sur le site https://koordinates.com/layer/101867-denver-colorado-american-community-survey-neigborhood-2010-2014/metadata/?type=fgdc.
df_survey = pd.read_csv(data_dir + 'american_community_survey_nbrhd_2013_2017.csv')
df_survey.head()
L'idée est d'étudier le lien éventuel entre le nombre de délits et les variables socio-économiques dont on dispose. Ainsi on va ajouter à cette table les colonnes représentant le nombre de crimes par quartier.
Puisque l'on va étudier la dangerosité des quartiers, le nombre de délits n'est pas suffisant, il faut aussi prendre en compte le nombre d'habitants dans chaque quartier. Ainsi on va définir une variable crimes_per_pop qui est le ratio $\frac{nb\_crimes\_sur\_la\_propriété}{nb\_habitants}$.
old_names = set(df_survey.NBHD_NAME)
right_names = set(df.NEIGHBORHOOD_ID)
replace_names = dict(zip(sorted(old_names), sorted(right_names)))
df_survey.NBHD_NAME = df_survey.NBHD_NAME.replace(replace_names)
df_survey.index = df_survey.NBHD_NAME
# tous les crimes
df_crimes = df[df.IS_CRIME == 1]
df_survey["nb_all"] = df_crimes.NEIGHBORHOOD_ID.value_counts()
# type de délits
for crime_type in set(df_crimes.OFFENSE_CATEGORY_ID):
df_crime_type = df_crimes[df_crimes.OFFENSE_CATEGORY_ID == crime_type]
df_survey["nb_" + str(crime_type)] = df_crime_type.NEIGHBORHOOD_ID.value_counts()
df_survey = df_survey.fillna(0)
df_survey["nb_violent_crimes"] = df_survey["nb_murder"] + df_survey["nb_robbery"] + df_survey["nb_aggravated-assault"]
df_survey["nb_property_crimes"] = df_survey["nb_larceny"] + df_survey["nb_burglary"] + df_survey["nb_auto-theft"] + df_survey["nb_theft-from-motor-vehicle"]
df_survey["nb_other_crimes"] = df_survey["nb_all-other-crimes"] + df_survey["nb_drug-alcohol"] + df_survey["nb_public-disorder"] + df_survey["nb_white-collar-crime"] + df_survey["nb_other-crimes-against-persons"]
df_survey["crimes_per_pop"] = df_survey["nb_all"]/df_survey["TTL_POPULATION_ALL"]
df_survey.iloc[:5, -18:]
Nous allons dans un premier temps analyser les délits en fonction des quartiers de Denver.
Nous allons commencer par regarder quels types de délits sont les plus courants à Denver et comment ils sont répartis dans le temps, en fonction des mois, des jours de la semaine et des heures.
cat_freq = df.OFFENSE_TYPE_ID.value_counts()
cat_freq = pd.DataFrame({"count": cat_freq.values, "category": cat_freq.index})
fig = px.bar(cat_freq.iloc[:20, :][::-1], y = "category", x = "count",
orientation = "h", color = "count",
title = "Les 20 types de délits les plus courants à Denver",
template = "plotly_white" ,
color_continuous_scale = px.colors.sequential.Teal)
fig.update(layout=dict(title=dict(x=0.5)))
fig.show()
fig = px.bar(cat_freq.iloc[-20:, :][::-1], y = "category", x = "count",
orientation = "h", color = "count",
title = "Les 20 types de délits les moins courants à Denver",
template = "plotly_white" ,
color_continuous_scale = px.colors.sequential.Teal)
fig.update(layout=dict(title=dict(x=0.5)))
fig.show()
Concernant la fréquence des types de délits à Denver, on peut remarquer que certains se comptent par dizaine de milliers en seulement 8 ans comme notamment les délits de la route, lorsque d'autres ne se produisent qu'une fois ou deux dans la même période. On a donc un écart d'échelle assez important entre les délits les plus courants et les moins courants.
df.index = df.index.rename("FIRST_OCC_DATE")
test = df.pivot_table(index='FIRST_OCCURRENCE_DATE', columns='OFFENSE_CATEGORY_ID',
aggfunc='size', fill_value=0).resample('M').sum().rolling(window=12).mean().dropna()
fig = make_subplots(rows=5, cols=3,
subplot_titles = test.columns)
i = 0
for row in range(5):
for col in range(3):
fig_tmp = go.Scatter(x = test.iloc[:, i].index, y = test.iloc[:, i].values)
fig.add_trace(fig_tmp, row = row+1, col = col+1)
i += 1
fig.update_layout(showlegend = False)
fig.show()
On constate que la tendance varie beaucoup en fonction du type de délit considéré, ainsi par exemple les assauts aggravés ont une tendance croissante ces dernières années, alors que les cambriolages sont en forte baisse.
months = ['Jan','Fev','Mar','Avr','Mai','Juin','Juil','Août','Sep','Oct','Nov','Dec']
violent_crimes_df = df[df["Violent Crime"] == 1]
property_crimes_df = df[df["Property Crime"] == 1]
other_crimes_df = df[df["Other Crimes"] == 1]
month_freq_violent_crimes = violent_crimes_df.MONTH.value_counts()
month_freq_violent_crimes = pd.DataFrame({"count": month_freq_violent_crimes.values,
"mois": month_freq_violent_crimes.index})
month_freq_property = property_crimes_df.MONTH.value_counts()
month_freq_property = pd.DataFrame(pd.DataFrame({"count": month_freq_property.values,
"mois": month_freq_property.index}))
month_freq_other = other_crimes_df.MONTH.value_counts()
month_freq_other = pd.DataFrame(pd.DataFrame({"count": month_freq_other.values,
"mois": month_freq_other.index}))
fig = make_subplots(rows=1, cols=3,
subplot_titles = ["Crimes violents", "Crimes sur la propriété", "Autres crimes"])
fig1 = px.bar(month_freq_violent_crimes, x = "mois", y = "count")
fig2 = px.bar(month_freq_property, x = "mois", y = "count")
fig3 = px.bar(month_freq_other, x = "mois", y = "count")
fig.add_trace(fig1["data"][0], row = 1, col = 1)
fig.add_trace(fig2["data"][0], row = 1, col = 2)
fig.add_trace(fig3["data"][0], row = 1, col = 3)
dic_xaxis = dict(
tickmode = 'array',
tickvals = list(range(1, 13)),
ticktext = months,
title = "Mois"
)
fig.update_layout(
xaxis1 = dic_xaxis,
xaxis2 = dic_xaxis,
xaxis3 = dic_xaxis,
yaxis_title = "Nombre de délits"
)
fig.show()
En ce qui concerne la répartition des délits par mois, on peut constater un nombre moins important de délits au mois de février pour chaque classe, ainsi qu'un pic en janvier et l'été en juillet-août.
On note quand même que l'échelle est différente pour le graphe des crimes violents, on ne peut pas conclure sur cette observation car cette classe ne regroupe que 3 types de délits alors que les deux autres en regroupent 5 chacune. On retrouvera cette différence d'échelle sur l'ensemble des graphes du fait que le nombre de crimes violents dans le jeu de données soit plus petit que celui des autres classes.
weekdays = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]
day_freq_violent_crimes = violent_crimes_df["DAY_OF_WEEK"].value_counts()
day_freq_violent_crimes = pd.DataFrame({"count": day_freq_violent_crimes.values,
"jour": day_freq_violent_crimes.index})
day_freq_property = property_crimes_df["DAY_OF_WEEK"].value_counts()
day_freq_property = pd.DataFrame(pd.DataFrame({"count": day_freq_property.values,
"jour": day_freq_property.index}))
day_freq_other = other_crimes_df["DAY_OF_WEEK"].value_counts()
day_freq_other = pd.DataFrame(pd.DataFrame({"count": day_freq_other.values,
"jour": day_freq_other.index}))
fig = make_subplots(rows=1, cols=3,
subplot_titles = ["Crimes violents", "Crimes sur la propriété", "Autres crimes"])
fig1 = px.bar(day_freq_violent_crimes, x = "jour", y = "count")
fig2 = px.bar(day_freq_property, x = "jour", y = "count")
fig3 = px.bar(day_freq_other, x = "jour", y = "count")
fig.add_trace(fig1["data"][0], row = 1, col = 1)
fig.add_trace(fig2["data"][0], row = 1, col = 2)
fig.add_trace(fig3["data"][0], row = 1, col = 3)
dic_xaxis = dict(
tickmode = 'array',
tickvals = list(range(0, 8)),
ticktext = weekdays,
title = "Jour"
)
fig.update_layout(
xaxis1 = dic_xaxis,
xaxis2 = dic_xaxis,
xaxis3 = dic_xaxis,
yaxis_title = "Nombre de délits"
)
fig.show()
On peut constater qu'il y a moins de délits des classes crime sur la propriété ou autres crimes le week-end qu'en semaine. A l'inverse, les crimes violents semblent être plus fréquents le week-end qu'en semaine.
hour_freq_violent_crimes = violent_crimes_df["HOUR"].value_counts()
hour_freq_violent_crimes = pd.DataFrame({"count": hour_freq_violent_crimes.values,
"heure": hour_freq_violent_crimes.index})
hour_freq_property = property_crimes_df["HOUR"].value_counts()
hour_freq_property = pd.DataFrame(pd.DataFrame({"count": hour_freq_property.values,
"heure": hour_freq_property.index}))
hour_freq_other = other_crimes_df["HOUR"].value_counts()
hour_freq_other = pd.DataFrame(pd.DataFrame({"count": hour_freq_other.values,
"heure": hour_freq_other.index}))
fig = make_subplots(rows=1, cols=3,
subplot_titles = ["Crimes violents", "Crimes sur la propriété", "Autres crimes"])
fig1 = px.bar(hour_freq_violent_crimes, x = "heure", y = "count")
fig2 = px.bar(hour_freq_property, x = "heure", y = "count")
fig3 = px.bar(hour_freq_other, x = "heure", y = "count")
fig.add_trace(fig1["data"][0], row = 1, col = 1)
fig.add_trace(fig2["data"][0], row = 1, col = 2)
fig.add_trace(fig3["data"][0], row = 1, col = 3)
dic_xaxis = dict(
tickmode = 'array',
tickvals = list(range(24)),
tickangle = 0,
tickfont=dict(size=8),
title = "Heure"
)
fig.update_layout(
xaxis1 = dic_xaxis,
xaxis2 = dic_xaxis,
xaxis3 = dic_xaxis,
yaxis_title = "Nombre de délits"
)
fig.show()
En règle général, on peut dire qu'il y a une baisse significative des délits à l'aube. Les crimes violents se passent en majorité entre 21h et 2h. Pour les deux autres classes, il semble y avoir des pics à plusieurs heures par exemple à 8h, 12h et 17h pour les crimes sur la propriété.
# écart moyen et standard des délits par jour
crimes_per_day = pd.DataFrame(df.resample('D').size())
crimes_per_day["MEAN"] = df.resample('D').size().mean()
crimes_per_day["STD"] = df.resample('D').size().std()
# limite de contrôle supérieure et limite de contrôle inférieure
UCL = crimes_per_day['MEAN'] + 3 * crimes_per_day['STD']
LCL = crimes_per_day['MEAN'] - 3 * crimes_per_day['STD']
fig = go.Figure()
fig1 = go.Scatter(y = crimes_per_day[0],
x = crimes_per_day.index,
line = dict(color = "blue"),
name = "nombre de délits")
fig2 = go.Scatter(x = crimes_per_day.index, y = UCL.values,
line = dict(dash = "dash",
color = "red"), name = "limite de contrôle supérieure")
fig3 = go.Scatter(x = crimes_per_day.index, y = LCL.values,
line = dict(dash = "dash",
color = "red"), name = "limite de contrôle inférieure")
fig4 = go.Scatter(x = crimes_per_day.index, y = crimes_per_day["MEAN"],
line = dict(color = "red"), name = "moyenne")
fig.add_trace(fig2)
fig.add_trace(fig1)
fig.add_trace(fig3)
fig.add_trace(fig4)
fig.update_layout(title = dict(x = 0.5, text = "Evolution du nombre de délits"),
xaxis_title = "Date", yaxis_title = "Nombre de délits")
fig.show()
print(f"Décomposition de la série")
decomposition = seasonal_decompose(crimes_per_day[0], period = 365)
trend = decomposition.trend
seasonal = decomposition.seasonal
residual = decomposition.resid
plt.subplot(411)
plt.plot(crimes_per_day[0], label = 'Original')
plt.legend(loc = 'best')
plt.subplot(412)
plt.plot(trend, label='Trend')
plt.legend(loc = 'best')
plt.subplot(413)
plt.plot(seasonal,label='Seasonality')
plt.legend(loc = 'best')
plt.subplot(414)
plt.plot(residual, label = 'Residuals')
plt.legend(loc = 'best',)
plt.tight_layout()
plt.show()
Il semblerait qu'il y ait une légère tendance haussière, ainsi qu'une saisonnalité annuelle. Nous allons vérifier cela à l'aide d'un test de stationnarité (KPSS).
Les hypothèses du test KPSS sont les suivantes :
def kpss_test(timeseries):
print ('Résultats du Test KPSS:')
# test kpss
kpsstest = kpss(timeseries, regression='c')
# récupération puis affichage des résultats
kpss_output = pd.Series(kpsstest[0:3], index = ['Test Statistic','p-value','Lags Used'])
for key,value in kpsstest[3].items():
kpss_output['Critical Value (%s)'%key] = value
print (kpss_output)
kpss_test(crimes_per_day[0])
La statistique de test étant supérieure à la valeur critique à 5%, nous rejetons donc l'hypothèse nulle et pouvons ainsi considérer que la série n'est pas stationnaire, ce qui confirme la tendance précédemment conjecturée.
On réalise une ACP où les individus sont les quartiers et les variables sont les nombres de délits pour chacune des 3 classes préalablement définies.
df_pca = df_survey.iloc[:, -4:-1]
df_pca
# ACP
from sklearn.decomposition import PCA
from sklearn import preprocessing
X = df_pca.values
X = preprocessing.normalize(X)
# Définition de la commande
pca = PCA()
# Composantes principales
C = pca.fit(X).transform(X)
C.shape
On représente les individus, c'est-à-dire les quartiers, sur les 2 premières composantes principales obtenues avec l'ACP.
cols = sns.color_palette("RdBu_r", n_colors = 78)
sns.palplot(cols)
print("\t \t \t \t Moins dangereux -> Plus dangereux")
# Représentation des individus
plt.figure(figsize = (10,8))
for i, j, nb, name in zip(C[:, 0], C[:, 1], df_survey["crimes_per_pop"].values, df_survey.index):
rank_danger = sorted(df_survey.crimes_per_pop).index(nb)
plt.scatter(i,j, color = cols[rank_danger])
plt.text(i, j, name, fontsize = 9, color = cols[rank_danger], rotation = 20)
#plt.ylim(-0.2, 0.2)
#plt.xlim(-0.2, 0.2)
plt.show()
On constate une nette séparation des quartiers en fonction du ratio de dangerosité défini précédemment.
On représente maintenant le cercle des corrélations.
(fig, ax) = plt.subplots(figsize = (12, 12))
for i in range(0, len(pca.components_[0])):
ax.arrow(0, 0, # Start the arrow at the origin
pca.components_[0, i], pca.components_[1, i], # 0 and 1 correspond to dimension 1 and 2
head_width = 0.1,head_length = 0.1, ec = "black")
plt.text(pca.components_[0, i] + 0.05, pca.components_[1, i] + 0.05, df_pca.columns.values[i], fontsize = 9)
an = np.linspace(0, 2 * np.pi, 100) # Add a unit circle for scale
plt.plot(np.cos(an), np.sin(an))
plt.axis('equal')
ax.set_title('Variable factor map')
plt.axhline(y = 0, color = "black", linestyle = '-')
plt.axvline(x = 0, color = "black")
plt.show()
Le cercle des corrélations montre que les variables représentant le nombre de crimes sur la propriété et le nombre d'autres crimes sont très bien représentées, alors que la variable sur les crimes violents est mal représentée. Les deux premières ont un coefficient de corrélation nul.
crimes_df = df[df["Property Crime"] == 1]
crimes_geo_df = crimes_df[["GEO_LAT", "GEO_LON"]].dropna(subset=['GEO_LAT', 'GEO_LON'])
years = sorted(set(crimes_geo_df.index.year))[:-1]
heat_data = []
for year in years :
df_year = crimes_geo_df[crimes_geo_df.index.year == year]
heat_data.append(df_year.values.tolist())
denver_map = folium.Map(location=[39.72378, -104.899157],
zoom_start=12,
tiles="CartoDB positron")
hm = plugins.HeatMapWithTime(heat_data, index = years, radius = 3)
hm.add_to(denver_map)
denver_map
La zone de chaleur principale se trouve dans le centre de Denver à l'est de l'autoroute. On constate moins de délits dans la périphérie de la ville, notamment aux alentours de l'aéroport situé au nord-est de la ville et le long des grands axes routiers.
Nous avons développé une application Shiny permettant de visualiser le nombre de crimes par quartier, avec l'option de sélectionner le type de crimes ainsi qu'une fenêtre de sélection temporelle.
IFrame(src='https://florian-goron.shinyapps.io/Denver2/', width=1000, height=500)
En sélectionnant différentes variables, on peut remarquer que le changement du type de crimes implique un changement de quartier. Ainsi par exemple, les délits de type drogue/alcool sont plus fréquents dans le centre de Denver, avec une échelle allant de 0 à 4000. En revanche les délits de type assauts aggravés sont concentrés dans un quartier du nord de Denver avec une échelle allant de 0 à 1000 seulement.
Ce Shiny nous permet d'émettre une hypothèse sur un lien entre le type de délits et le quartier, c'est pourquoi nous allons par la suite nous intéresser aux caractéristiques socio-économiques des quartiers de Denver afin de confirmer cette hypothèse.
Nous allons dans un premier temps représenter les 10 quartiers les plus dangereux, c'est-à-dire les quartiers ayant le plus grand nombre de délits par habitant, ainsi que les 10 quartiers les moins dangereux.
neigh_ratio = df_survey["crimes_per_pop"].sort_values()
neigh_ratio = pd.DataFrame({"crimes_per_pop": neigh_ratio.values,
"quartier": neigh_ratio.index})
safest = neigh_ratio.iloc[:10]
dangerous = neigh_ratio.iloc[-10:]
dangerous
fig = make_subplots(rows=1, cols=2,
subplot_titles = ["10 quartiers les plus dangereux",
"10 quartiers les moins dangereux"])
fig1 = px.bar(neigh_ratio.iloc[-10:], x = "crimes_per_pop", y = "quartier", orientation = "h")
fig.add_trace(fig1["data"][0], row = 1, col = 1)
fig1 = px.bar(neigh_ratio.iloc[:10], x = "crimes_per_pop", y = "quartier", orientation = "h")
fig2 = px.bar(neigh_ratio.iloc[:10], x = "crimes_per_pop", y = "quartier", orientation = "h")
fig.add_trace(fig2["data"][0], row = 1, col = 2)
fig.show()
On constate qu'en moyenne dans les quartiers les plus dangereux on a environ 10 à 20% de délits en plus par habitant que dans les quartiers les moins dangereux.
On représente la matrice de corrélation en sélectionnant seulement les variables socio-économiques qui ont une corrélation supérieure à 0.5 avec la variable du nombre de crimes sur la propriété, toujours dans l'optique de répondre à la problématique.
cols = list(df_survey.iloc[:, :-19].columns) + ["nb_property_crimes"]
sns.set(style="white")
corr = df_survey[cols].corr()
# on sélectionne les vars corr >= 0.5 avec nb_property_crimes
strong_corr = corr[abs(corr["nb_property_crimes"]) > 0.5].index
corr = corr.loc[strong_corr, strong_corr]
mask = np.triu(np.ones_like(corr, dtype=np.bool))
f, ax = plt.subplots(figsize=(15, 15))
cmap = sns.diverging_palette(220.0, 10.0, as_cmap=True)
ax = sns.heatmap(corr, mask=mask, cmap=cmap, vmax=1, vmin=-1, center=0,
square=True, linewidths=.5, cbar_kws={"shrink": .5},
annot = True, annot_kws = {"size": 8})
len(strong_corr) - 1
On a 38 variables qui sont fortement corrélées à la variable nb_property_crimes, les plus fortes corrélations ayant lieu avec les variables concernant l'année de construction du logement de l'habitant. On note également de très fortes corrélations entre les variables socio-économiques, comme par exemple NOT_ENROLLED (non inscrit dans un système éducatif) et ONLY_ENGLISH_LNG (anglais comme seule langage) qui ont une corrélation de 0.91.
On fait une ACP en utilisant les 38 variables socio-économiques les plus corrélées à la variable nb_property_crimes.
df_pca = df_survey.loc[:, strong_corr[:-1]]
for col_num in np.where(df_pca.dtypes == np.object)[0]:
col = df_pca.columns[col_num]
df_pca[col] = pd.to_numeric(df_pca[col], errors='coerce')
df_pca = df_pca.fillna(0)
df_pca
# ACP
X = df_pca.values
X = preprocessing.normalize(X)
# Définition de la commande
pca = PCA()
# Composantes principales
C = pca.fit(X).transform(X)
C.shape
On représente les individus, c'est-à-dire les quartiers, sur les 2 premières composantes principales obtenues avec l'ACP.
cols = sns.color_palette("RdBu_r", n_colors=78)
sns.palplot(cols)
print("\t \t \t \t Moins dangereux -> Plus dangereux")
# Représentation des individus
plt.figure(figsize=(10,8))
for i, j, nb, name in zip(C[:, 0], C[:, 1], df_survey["crimes_per_pop"].values, df_survey.index):
rank_danger = sorted(df_survey.crimes_per_pop).index(nb)
plt.scatter(i,j, color = cols[rank_danger])
plt.text(i, j, name, fontsize = 9, color = cols[rank_danger], rotation = 20)
#plt.ylim(-0.2, 0.2)
#plt.xlim(-0.2, 0.2)
plt.show()
On constate que les quartiers les moins dangereux se retrouvent en majorité en haut à gauche du graphe alors que les quartiers plus dangereux en rouge s'éparpillent vers la droite et vers le bas.
(fig, ax) = plt.subplots(figsize=(12, 12))
for i in range(0, len(pca.components_[0])):
ax.arrow(0, 0, # Start the arrow at the origin
pca.components_[0, i], pca.components_[1, i], # 0 and 1 correspond to dimension 1 and 2
head_width = 0.1,head_length=0.1, ec = "black")
plt.text(pca.components_[0, i] + 0.05, pca.components_[1, i] + 0.05, df_pca.columns.values[i], fontsize = 9)
an = np.linspace(0, 2 * np.pi, 100) # Add a unit circle for scale
plt.plot(np.cos(an), np.sin(an))
plt.axis('equal')
ax.set_title('Variable factor map')
plt.axhline(y=0, color = "black", linestyle='-')
plt.axvline(x=0, color = "black")
plt.show()
On constate sur ce cercle des corrélations que la seule variable qui est bien représentée est la variable "WHITE" qui indique le nombre de personnes de couleur blanche. L'orientation de cette variable sur le cercle en comparaison avec le graphe des individus semble indiquer que le nombre de personnes de couleur blanche est lié à un faible taux de délits par habitant.
from IPython.core.display import display, HTML
IFrame(src='./reg_lin.html', width=1000, height=700)
Pour rappel, le but de ce projet était de déterminer s'il était possible d'expliquer le taux important de crimes contre la propriété dans la ville de Denver au travers de caractéristiques socio-économiques des habitants par quartier.
Les outils de visualisation utilisés, c'est-à-dire la carte de chaleur ainsi que l'application Shiny, nous ont permis dans un premier temps d'avoir une première intuition sur un possible lien entre le type de délits commis et le quartier dans lequel il avait été commis.
On a ensuite cherché à confirmer cette intuition en ajoutant dans notre étude les variables socio-économiques. La matrice de corrélation nous a montré que 38 variables étaient corrélées positivement avec les crimes contre la propriété, mais finalement, en testant ces 38 variables dans l'ACP, nous ne pouvons conclure que sur un lien entre les personnes de race blanche et un faible nombre de délits par habitant.
Enfin, nous avons décidé de nous intéresser uniquement aux cambriolages, qui est un type de délits faisant partie de la classe des crimes contre la propriété. Nous avons ainsi sélectionné les variables explicatives grâce à la régression linéaire, et pouvons finalement conclure sur six variables liées aux cambriolages, variables concernant la race de l'habitant, le revenu ainsi que l'âge.
Pour conclure sur ce projet, les données dont nous disposions ne nous ont pas permis de pouvoir affirmer avec certitude un possible lien entre les crimes contre la propriété et les caractéristiques socio-économiques des habitants de Denver. Néanmoins, c'est un jeu de données très complet que nous n'avons pas pu exploiter dans son ensemble et qui, couplé avec d'autres jeux de données disponibles sur le même site que le jeu de données initial, pourrait permettre de nouvelles approches pour répondre à la problématique.