<jupyter_start><jupyter_text><jupyter_code>import pandas as Pandas
import psycopg2<jupyter_output>/usr/local/lib/python3.6/dist-packages/psycopg2/__init__.py:144: UserWarning: The psycopg2 wheel package will be renamed from release 2.8; in order to keep installing from binary please use "pip install psycopg2-binary" instead. For details see: <http://initd.org/psycopg/docs/install.html#binary-install-from-pypi>.
  """)<jupyter_text>Debido a la gran cantidad de datos y su estructura, este dataset se presta mucho para almacenarlo en una Base de Datos. Este dataset en últimas tiene solo una variable numérica (la edad) y tanto la fecha como la hora son tipo TIMESTAMP. Mientras que los datasets pesaban en total 59MB, gracias a la factorización hecha en la DB pasaron a pesar apenas 3MB en total. Asi, tomé la decisión de tercerizar esos datos a una Base de Datos para comprimirla. La base de datos está subida en un SaaS de PostgreSQL llamado ElephantSQL que les permite hostear hasta 20MB de información. El diagrama de la DB lo pueden ver [aquí](https://https://dbdiagram.io/d/5e7cfa144495b02c3b88d373).<jupyter_code>#conexión a la Base de Datos
#parametros de conexion a la DB
parametrosDict = {
    "host":"drona.db.elephantsql.com",
    "database":"autkzlzm",
    "user":"autkzlzm",
    "password":"<PASSWORD>"
}
#esto retorna queries como dataframes
def queryComoDataFrame(sqlQuery):
  DBConnection = None #comenzamos creando un objeto conexión para conectarnos a la DB con los parámetros dados
  resultDataFrame = None #comenzamos con un objeto dataframe vacío.
  try:
    # conectar usando el método connect de pyscopg2
    print('Connecting to the PostgreSQL database...')
    DBConnection = psycopg2.connect(**parametrosDict)
    #usar el Pandas DataFrame para recibir el comando
    resultDataFrame = Pandas.read_sql_query(sqlQuery, DBConnection)
    DBConnection.close()
    return resultDataFrame
  except (Exception, psycopg2.DatabaseError) as error:
    print('Error en el Query:',error)
    return None
  #si algo no ha sido cubierto en los casos anteriores, cerrar la conexión a la base de datos
  finally:
    if DBConnection is not None:
      DBConnection.close()
    print('en caso finally: query ejecutado, resultados en un data frame.')<jupyter_output><empty_output><jupyter_text>Para construir una tabla de contingencia sobre dos variables categóricas debemos contar incidencias de las categorías de ambas. Queremos ver independencia entre estas variables categóricas. **Para este dataset en particular, estamos viendo la independencia entre localidades y años, entre años de incidentes criminales y si la incidencia criminal es independiente del año. (la pregunta es filosófica). **En este caso, usé una función SQL para contar el número de incidentes en una localidad por año. Pillen el código para contar el número de incidentes en un año y en una localidad:```CREATE OR REPLACE FUNCTION numIncidentesEnLocalidadAno(codLocalidad VARCHAR, ano INTEGER) RETURNS INTEGERLANGUAGE plpgsqlAS $$BEGIN RETURN count(incidentes.incidente_id) FROM incidentes WHERE incidentes.codigo_localidad = codLocalidad AND EXTRACT(YEAR FROM incidentes.fecha) = ano;END;$$;```Esto se puede exportar a Python perfectamente. Sin embargo, prefería mantenerlo en el *Back-End*. Usando esta función es fácil generar una vista SQL que es donde voy a construir la tabla de contingencia ```CREATE VIEW incidentesAnuales ASSELECT localidades.codigo_localidad,       localidades.nombre_localidad,       numIncidentesEnLocalidadAno(localidades.codigo_localidad, 2010) as "2010",       numIncidentesEnLocalidadAno(localidades.codigo_localidad, 2012) as "2012",       numIncidentesEnLocalidadAno(localidades.codigo_localidad, 2013) as "2013",       numIncidentesEnLocalidadAno(localidades.codigo_localidad, 2014) as "2014",       numIncidentesEnLocalidadAno(localidades.codigo_localidad, 2015) as "2015",       numIncidentesEnLocalidadAno(localidades.codigo_localidad, 2018) as "2018",       numIncidentesEnLocalidadAno(localidades.codigo_localidad, 2019) as "2019"from localidadesgroup by localidades.codigo_localidadORDER BY localidades.codigo_localidad;```De modo tal que puedo extraer esa tabla de contingencia de la Base de Datos ya estructurada y desde Python me dedico a hacer cálculos.<jupyter_code>#vamos a mostrar por año el número de incidentes
queryIncidentesAnuales = queryComoDataFrame("SELECT  * from incidentesAnuales")
queryIncidentesAnuales<jupyter_output>Connecting to the PostgreSQL database...
en caso finally: query ejecutado, resultados en un data frame.<jupyter_text>Ahora, podemos limpiar este DataFrame para quitar campos que no nos interesan o que podrían arruinar el cálculo. A saber no hay incidentes registrados en la Localidad de Sumapaz y de nada nos sirve el código de localidad.<jupyter_code>#vamos a quitar el registro de la localidad de Sumapaz que no ofrece nada de información
queryIncidentesAnuales = queryIncidentesAnuales.loc[queryIncidentesAnuales['nombre_localidad']!='SUMAPAZ']<jupyter_output><empty_output><jupyter_text>Tablas de contingencia se pueden hacer en Excel, R, Stata, etc. Lo bonito de hacerlas en Python es que podemos hacer tablas *asociativas* usando los nombres de las categorías de la variable como hacemos aquí con la función set_index(). Esto nos va a permitir hacer las sumas que debemos hacer más fácilmente.<jupyter_code>queryIncidentesAnuales = queryIncidentesAnuales.drop(columns=['codigo_localidad'])
queryIncidentesAnuales = queryIncidentesAnuales.set_index('nombre_localidad')
queryIncidentesAnuales<jupyter_output><empty_output><jupyter_text>Para hacer una prueba de independencia en tablas de contingencia debemos calcular la suma por fila. Comenzamos con la suma por columnas. Estas nos dan la base para calcular probabilidades, usando el viejo esquema de >>>$p = \frac{\text{eventos favorables}}{\text{eventos totales}}$<jupyter_code>#vamos a generar un diccionario que contenga la suma por filas
sumaLocalidades = dict(queryIncidentesAnuales.sum(axis = 1, skipna = True))
#vamos a generar un diccionario que contenga la suma por columnas
sumaAnos = dict(queryIncidentesAnuales.sum(axis=0,skipna=True))<jupyter_output><empty_output><jupyter_text>Ahora debemos calcular el gran total por fila y por columna<jupyter_code>totalLocalidades = 0
totalAnos = 0
for loc in sumaLocalidades.keys():
  totalLocalidades += sumaFilas[loc]
for ano in sumaAnos.keys():
  totalAnos += sumaAnos[ano]
print(totalLocalidades, totalAnos)
granTotal = totalLocalidades<jupyter_output>17647 17647<jupyter_text>Ahora podemos construir la *tabla de probabilidades* con esta información. Cada posición de la tabla de contingencia está dada por>> $t_{ij} = \frac{S_iS_j}{T}$ donde $i$ denota la i-ésima fila y $j$ la j-ésima columna. Este cálculo deriva de la regla de probabilidad condicional.<jupyter_code>localidades = list(sumaLocalidades.keys())
anos = list(sumaAnos.keys())
tablaContingencia = Pandas.DataFrame(columns = ['nombre_localidad']+anos)
for loc in localidades:
  filaDict = {}
  filaDict['nombre_localidad'] = loc 
  for ano in anos:
    contingencia = sumaLocalidades[loc]*sumaAnos[ano]/granTotal
    filaDict[ano] = contingencia
  tablaContingencia = tablaContingencia.append(filaDict,ignore_index=True)
tablaContingencia = tablaContingencia.set_index('nombre_localidad')
tablaContingencia<jupyter_output><empty_output><jupyter_text>Ahora, el estadístico matricial de prueba es la suma de la diferencia entre la tabla original y la contigencia (una suerte de norma matricial). La fórmula para la casilla de la matriz que necesitamos es:>>$r_{ij} = \frac{(A_{ij}-T_{ij})^2}{T_{ij}}$Noten que esto es una suerte de error porcentual (elevando al cuadrado)<jupyter_code>tablaDiferencia = (queryIncidentesAnuales - tablaContingencia)**2/tablaContingencia
tablaDiferencia
from scipy import stats
gradosDeLibertad = (len(tablaDiferencia)-1)*(len(tablaDiferencia.columns)-1)
valorObtenido = tablaDiferencia.values.sum()
print("grados de libertad:", len(tablaDiferencia)-1,"X",len(tablaDiferencia.columns)-1,"=",gradosDeLibertad)
print("Estadístico de prueba:",valorObtenido)
chi2_estadistico, p_values, dof, ex = stats.chi2_contingency(queryIncidentesAnuales.values)
print("===Chi2 Stat===")
print("Estadístico Chi2:",chi2_estadistico)
print("grados de libertad:",dof)
print("p-value",p_val)<jupyter_output>grados de libertad: 19 X 6 = 114
Estadístico de prueba: 9162.950394857253
===Chi2 Stat===
Estadístico Chi2: 9162.950394857253
grados de libertad: 114
p-value 0.0
{'fraction_non_alphanumeric': 0.07675155498180965, 'fraction_numerical': 0.018894495951179438, 'mean_word_length': 6.155331654072208, 'pattern_counts': {'":': 4, '<': 35, '<?xml version=': 0, '>': 42, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '17067', 'license': 'MIT', 'n_tokens_mistral': 2953, 'n_tokens_neox': 2811, 'n_words': 871, 'path': 'tablasDeContingencia_datosBogota.ipynb', 'repo_name': 'camilorey/material_clases', 'size': '8543'}