Proyecto 1: Brecha Digital

Preparación de datos

Basado en el reporte "La Brecha Digital en California"

Pregunta(s) de Investigación:

  1. ¿Qué porcentaje de hogares en el estado X tiene acceso a internet de alta velocidad?
  2. ¿Varía este número según los grupos demográficos? (en este caso raza/etnia.

Meta:

  • Explorar los archivos de datos (acsdata.data.gz) y crear un conjunto de datos analítico (un archivo derivado enfocado específicamente en análisis en mano).

Contexto:

Obtenimos datos de la encuesta American Community Survey (ACS) de IPUMS.
Contiene características demográficas:

  • edad
  • sexo
  • raza/etnia

e indicadores geográficos:

  • estado
  • condado

Paso 1: Prepara tu entorno de trabajo.

Importa las bibliotecas necesarias y crea objetos Path (ruta de archivo). Esto grarantiza reproducibilidad en distintos sistemas operativos (Windows utiliza \ en lugar de / para separar los nombres de archivos.

Necesitamos:

  1. pandas para trabajar con los datos.
  2. pathlib, y más específicamente su objeto Path, para trabajar con rutas de archivos. Esto asegura que nuestro código funcione en Windows (que utiliza \ en sus rutas) y MacOS/Linux (los cuales utilizan /).
  3. datetime - tip: Existen sistemas de control de versiones para datos pero etiquetar tus archivos de datos (cuando no son masivos) no es un mal primer paso si estás comenzando.
In [1]:
# Prepara tu entorno de trabajo
import _____ as pd
from _____ import Path
from datetime import datetime as dt
hoy = __.today().strftime("%d-%m-%y")

print(hoy)
06-05-19

nota: Aunque estés utilizando Windows puedes escribir / en tus rutas de archivo.

In [2]:
# Directorio de datos y rutas
RUTA_DATOS_EN_BRUTO = ____("../datos/brutos/")
RUTA_DATOS_________ = ____("../datos/interinos/")
RUTA_______________ = ____("../datos/procesados/")
___________________ = ____("../datos/finales/")

NOTA: He incluido un script de python llamado herramientas.py con la función arbol la cual muestra el árbol de directorios. La obtuvé de el tutorial de RealPython's sobre el módulo pathlib).

de nuestro script herramientas importa arbol para utilizarla
In [ ]:
 
In [ ]:
arbol(________)

Paso 2: Carga y explora los datos

Con pandas leer archivos de datos es tan fácil como escribir .read_csv(RUTA_AL_ARCHIVO_CSV) y esto es suficiente la mayoría del tiempo. La función read_csv() de Pandas es tan poderosa que incluso lee archivos comprimidos sin tener que especificar algún otro parametro. Prueba lo siguiente:

datos = pd.read_csv(RUTA_DATOS_EN_BRUTO / 'acs_data.csv.gz')
datos.head()

Asegurate de cambiar RUTA_DE_DATOS_EN_BRUTO a lo que seaque hayas llamado tu variable a la que asignaste el objeto Path con la ruta a tus datos en bruto.

In [ ]:


IPUMS ofrece algunos formatos de datos que pueden ser aún más útiles [docs]:

Además del archivo de datos ASCII, el sistema crea un archivo de sintaxis de paquetes estadísticos para acompañar cada extracto. El archivo de sintaxis está diseñado para leer los datos ASCII mientras se aplican las variables de valor y las etiquetas apropiadas. SPSS, SAS y Stata son compatibles. Debe descargar el archivo de sintaxis con el extracto o no podrá leer los datos. El archivo de sintaxis requiere una edición menor para identificar la ubicación del archivo de datos en su computadora local.

En este caso, usaremos un archivo Stata (.dta). La razón principal es que los archivos .dta pueden almacenar etiquetas de valor quepandas luego puede leer y convertir columnas en columnas Categóricas en nuestro marco de datos de pandas. Esto 1) ahorra memoria, y 2) es una buena práctica porque ciertas ciencias sociales en serio, en serio en serio, y en serio en serio en serio aman a Stata, por lo que sus conjuntos de datos interesantes son probablemente archivos .dta.

Sin embargo, pandas no puede leer.dta comprimido directamente como lo hace con los archivos .csv. Afortunadamente, IPUMS utiliza el formato comprimido gzip y python incluye un módulogzip en su biblioteca estándar.

Importa gzip y prueba lo siguiente:

with gzip.open(RUTA_DATOS_EN_BRUTO / 'acs_data.dta.gz') as archivo:
    datos = pd.read_stata(archivo)

y muestra las primeras cinco filas de tu datos DataFrame.

In [ ]:
# importa gzip y carga tus datos
In [ ]:
# muestra las primeras 5 filas

Paso 3: Familiarizate con el conjunto de datos.

Ya hemos visto .head() - el método de pandas que mostrará las primeras 5 filas de tu DataFrame. Esto te da una idea de cómo se ven sus datos. Sin embargo, hay mucho más .info() que puede salir de su marco de datos. También puede simplemente pedirle al DataFrame si los .describe()...

In [ ]:
# averigua más info de tu DataFrame
datos.____()
In [ ]:
# describe tus datos
datos.____()

Comprueba la 'forma' de tus datos con su atributo .shape. Note la falta de paréntesis.

In [ ]:
datos._____

Paso 4: Recorta tus datos

En este momento estás trabajando con tu archivo maestro: un conjunto de datos que contiene todo lo que podría necesita para su análisis. Realmente no deseas modificar este conjunto de datos porque podría estar usándolo para otros análisis. Por ejemplo, vamos a analizar el acceso a Internet de alta velocidad en un estado de tu elección, pero la próxima semana es posible que desees realizar el mismo análisis en otro estado o tal vez solo en un condado específico. Para asegurarse de que puedas reutilizar tus datos y tu código más adelante, creamos un archivo de análisis, un conjunto de datos que contiene solo los datos necesarios para este análisis específico a mano.

Primero, solo estamos interesados en encontrar la _ "Brecha Digital" de un estado en este momento. El archivo maestro contiene datos de los 50 estados y el distrito de Columbia.

Lo que quieres hacer es encontrar todas las filas donde el statefip coincide con el nombre de tu estado. Esto se llama indexación booleana.

Prueba lo siguiente:

datos['statefip'] == 'ohio'

Nota: Tu puedes cambiar 'ohio' a cualquiera de los 50 estados o 'district of columbia' para DC.

In [ ]:
# Prueba indexación booleanda
___['______'] == '_______'

Esto devolverá un pandas.Series de booleanos (Trues y Falses) que luego puede usar para filtrar las filas innecesarias.

Es una buena práctica guardar esta Serie como una variable al principio de tu código (si la conoces de antemano) o justo antes de usarla en caso de que use estas condiciones en más de un lugar. Esto te ahorrará tiempo si decide cambiar el valor que está comparando, 'ohio' para' california' por ejemplo.

mascara_estado = (datos['statefip'] == 'ohio')
datos[mascara_estado].head()
In [ ]:
# hazlo tu
mascara_estado = (________________________ == _______)
datos[mascara_estado].____()

Guardemoslo en una variable con un nombre más útil:

datos_estatales = datos[mascara_estado].copy()

Tienes que utilizar .copy() para crear copias reales de los datos. Si ejecutas:

datos_estatales = datos[mascara_estado]

datos_estatales sería lo que la documentación de pandas se refiere como una vista de DataFrame datos. Esto puede tener consecuencias más adelante si modificas estos DataFrames. Muchas veces recibirás solo una advertencia y tu código se ejecutará tal como lo esperabas - pero, ¿para qué tomar riesgos?

In [ ]:
# asigna tu nuevo DataFrame a la variable datos_estatales
datos_estatales = __________.copy()

Ahora, veamos que .columns tenemos en nuestro DataFrame. Puedes acceder esta información de la misma manera que encontramos la "forma" hace un rato.

In [ ]:
datos_estatales._____

¿Hay columnas de las cuales tienes seguridad que no vas a necesitar?
Si no estás 90% seguro de que no vas a necesitar una columna, no te deshagas de ella.

Eliminar columnas es tan fácil como utilizar .drop() en tu DataFrame.

datos_estatales.drop(columns = ['lista', 'de', 'columnas', 'pa', 'eliminar'])
In [ ]:
 

Si hay columnas que tu crees que no vas a necesitar pero no tienes mucha seguridad de que ese es el caso, deberías explorarlas.

Las columnas de los DataFrames de pandas son pandas.Series y tienen métodos y atributos como los DataFrames.

Exploremos la columna gq que representa los valores de la variable Group Quarters. La documentación de IPUMS define Group Quarters así:

Group quarters son en su mayoría instituciones y otras viviendas para grupos, por ejemplo, barracas militares y dormitorios.

Exploremos que valores unicos (.unique()) tiene la Serie datos_estatales['gq']...

In [ ]:
 

Tambien podemos ver las cuentas totales de los sus valores (.value_counts()) lo que nos daría una mejor idea de que tan útil esta columna podría ser.
Por ejemplo, si una columna tiene 2 valores pero 99% de sus observaciones tiene un valor y el resto tiene otro - en este caso, podrías deshacerte de esa columna ya que no agregaría mucho valor a tu análisis.

Hay algunas variables donde el 100% de sus filas tienen el mismo valor \*tos\* \*tos\* datos_estatales['year']

In [ ]:
 

De la documentación de IPUMS:

Hay tres definiciones un poco diferentes de group quarters en IPUMS. Para el periodo 1940-1970 (excluyendo el conjunto de datos 100% de 1940), group quarters eran unidades de vivienda con 5 o más individuos sin relación a la cabeza de la vivienda. Antes de 1940 y en 1980-1990, unidades con 10 o más individuos sin relación a la cabeza de la vivienda eran considerados group quarters. En el censo del 2000, 2010, la ACS y la PRCS, ningún límite fue aplicado; para que un hogar fuera considerado group quarter, tenía que estar en una lista de group quarters que el Censo mantiene continuamente. En años pasados, una lista similar fue utilizada, con la regla sobre personas-sin-relación immpuesta como seguridad.

Por esta razón, y por el hecho de que la gran mayoría de nuestras observaciones caen bajo la definición de 1970 y 1990, estas son a las que nos apegaremos para nuestro análisis.

Creemos otra mascara para filtrar todos los hogares que no caen bajo nuestra definición

Para evaluar multiples condiciones utilizamos los operadores & y | ("Y" y "O", respectivamente).

In [ ]:
mascara_hogar = ( PRIMERA CONDICION ) | ( SEGUNDA CONDICION )
Sobre columnas categóricas Otro valor agregado al utilizar variables de tipo categórico es que es que los valores son ordenados puedes utilizar los operadores < y > para evaluar condiciones.
    mascara_hogar = (datos_estatales['gq'] <= 'additional households under 1990 definition')
    
Nota sobre .copy() Porque estamos sobreescribiendo datos_estatales no necesitas utilizar .copy() pero no nos afecta utilizarlo. Además, si eres principiante en pandas es buena practica para cuando en serio necesites utilizar .copy().
In [ ]:
datos_estatales = datos_estatales[mascara_hogar].______()

Para este momento ya estas muy cerca de crear un datos_analisis DataFrame. Hasta ahora:

  1. Mantuviste los datos de un solo estado y te deshiciste del resto.
  2. Mantuviste los datos de aquellos hogares en los que tenemos interés y te deshiciste del resto.

Nuestra pregunta de investigación 1 es: "¿Qué porcentage de hogares en el estado X tiene acceso a internet de alta velocidad?"

Matemáticamente, $$ \frac{hogares\ con\ internet\ de\ alta\ velocidad}{hogares\ en\ el\ estado}$$

Tu conjunto de datos datos_estatales ahora contiene todo lo que necesitas para encontrar la respuesta.


Paso 5: Guarda tus datos

Ahora que haz recordado tu archivo maestro en un conjunto de datos enfocado para tu análisis, deberías guardarlo.

Hemos estado trabajando con un archivo .dta y sale mejor mantenerlo así.

Prueba lo siguiente:

datos_estatales.to_stata(RUTA_DATOS_INTERINOS / f'datos_estatales-{hoy}.dta', write_index = False)

Un par de cosas:

  1. Estamos utilizando f-strings para etiquetar nuestro archivo de datos con la fecha de hoy.
  2. Estamos apagando la bandera write_index para que al grabar nuestro archivo no se le agregue una columna "index" (índice). Es nuestro conjunto de datos el índice no tiene valor. En otros análisis puede que tengas un índice que tenga algún valor o significado - en esos casos no vas a querer apagar esta bandera.
In [ ]:
 

Paso 6: Bonus

¿Qué tal si cambiaramos nuestra pregunta de investigación un poco? de
"¿Qué porcentaje de hogares en el estado X tienen acceso a internet de alta velocidad?"
a
"¿Qué porcentaje de hogares con niños y niñas de edad escolar en el estado X tiene acceso a internet de alta velocidad?"

Esto sería una estadística interecante para los creadores de políticas públicas, especialmente si encontramos diferencias entre grupos demográficos (pregunta de investigación 2).

El reto aquí es que nuestra unidad de observación en el archivo datos_estatales es una persona (ponderada) y que ahora queremos filtrar aquellos hogares sin niños o niñas de edad escolar. Esto puede sonar un poco complicado a primera vista pero solo requiere modificar nuestro flujo de trabajo un poco.

Necesitamos hacer unas cuantas cosas:

  1. Definir a que nos referimos con niños/niñas de edad escolar.
  2. Crear una mascara para identificar todos los hogares donde esten estos niños o niñas.
  3. Crear una lista sin duplicados de estas identificaciones de hogar ('serial')
  4. Usar esta lista para eliminar todas las observaciones innecesarias.

Paso 6.1: Niños/Niñas de edad escolar

La mayoría de las personas concordamos que edad escolar (de primaria a preparatoria) es de 6 a 17 años. Algunas personas están mas interesadas en Kindergarden - Preparatoria (de 5 a 17 o 18 años). Algunas más no incluirían personas de 18 años. Lo importante es saber debes poder defender el porque de que cualquier rango.

Para este análisis, yo sugeriría que usemos 5 - 18 años (Kinder a Preparatoria) pero tu puedes escoger cualquier rango que desees. ¿Qué tal edad de preparatoria (14-18)? Eso sería interesante, lo más probable es que necesites acceso a internet de alta velocidad en la preparatoria más que cuando estas en kindergarden.

In [ ]:
mascara_ninxs = (datos_estatales['age'] >= ___) & (___________________ <= )
¿Qué tipo de datos es la Serie datos_estatales['age'] ? Categórica. Esto significa que aunque los valores de esta columna se vean como números, la realidad es que son etiquetas de valor las cuales son cadenas de caracteres o "strings".

Ahora que tenemos nuestra máscara podemos utilizarla para crear una lista de hogares con niños/niñas.

Anteriormente, aplicamos una máscara a un DataFrame y la guardamos en una variable. Ahora, vamos a dar un paso más y tomar una sola columna del marco de datos filtrado.

Intentalo tu primero.

sugerencia ¿Cómo tomamos y exploramos una sola columna de un DataFrame anteriormente?
In [ ]:
hogares_con_ninxs = _________________________________________
In [ ]:
hogares_con_ninxs.head()

¿Recuerdas como eliminamos (drop) columnas al inicio?

¿Cómo crees que podemos eliminar duplicados?

respuesta
.drop_duplicates()
In [ ]:
 

Ahora que tienes una lista de valores únicos de los hogares con niños/niñas, solo tienes que revisar si un valor de la columna serial de nuestro marco de datos datos_estatales también en nuestra series hogares_con_ninxs.

respuesta
minimo intentalo
bueno pues
en inglés como dirías "está en" .isin(), obvis.
In [ ]:

Grabemos esto como datos_para_analisis en nuestra computadora.

In [ ]:
datos_para_analisis = _____________________
datos_para_analisis.to_stata(RUTA_DATOS_INTERINOS / f'datos_para_analisis-{hoy}.dta', write_index = False)
In [ ]: