#!/usr/bin/env python # coding: utf-8 # # Proyecto 1: Brecha Digital # ### Preparación de datos # # #### Basado en el reporte ["La Brecha Digital en California"](https://www.ppic.org/wp-content/uploads/jtf-californias-digital-divide-in-spanish.pdf) # ## 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](https://usa.ipums.org/usa/).
# Contiene características demográficas: # - edad # - sexo # - raza/etnia # # e indicadores geográficos: # - estado # - condado # *** # #### Paso 1: Prepara tu entorno de trabajo. # # **Import**a 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) # _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`](https://realpython.com/python-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: # # ```python # 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]](https://usa.ipums.org/usa-action/faq#ques12): # > 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* que` pandas` 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ódulo` gzip` en su biblioteca estándar. # # **Import**a gzip y prueba lo siguiente: # ```python # 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: # ```python # 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. # # ```python # 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: # # ```python # datos_estatales = datos[mascara_estado].copy() # ``` # Tienes que utilizar `.copy()` para crear copias _reales_ de los datos. Si ejecutas: # ```python # 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. # # ```python # 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](https://usa.ipums.org/usa-action/variables/GQ#description_section) 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](https://usa.ipums.org/usa-action/variables/GQ#comparability_section) 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: # ```python # 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 = _____________________ # ```python # datos_para_analisis.to_stata(RUTA_DATOS_INTERINOS / f'datos_para_analisis-{hoy}.dta', write_index = False) # ``` # In[ ]: