Módulo 2 · Datos

Lección 2 de 6

Tablas y valores faltantes

En esta páginaEl problema

El problema

Recibes sensores-aula.csv y una solicitud sintética: preparar un subconjunto con las muestras cuyo co2_ppm sea mayor o igual que 500. Antes de responder, necesitas saber qué leyó pandas, localizar irregularidades y comprobar que la tabla original permanezca intacta.

El archivo contiene ocho observaciones y cinco columnas. Estas tres filas permiten ver su estructura y el faltante intencional:

muestra_idtemperatura_chumedad_pctco2_ppmzona
A-00122.461420norte
A-00223.158435norte
A-00321.8celda vacía415sur

Una fila representa una observación sintética. Cada columna conserva una variable con significado y unidad. La celda vacía permite detectar una irregularidad. Todavía no autoriza rellenar, eliminar ni imputar.

El objetivo es producir una nota local que describa la estructura inferida, distinga índice e identificador, registre faltantes y responda la solicitud sin modificar la fuente.

Esquema y tipos inferidos

Al leer un CSV, pandas decide cuántas filas y columnas hay, qué nombres usar y qué tipo asignar a cada columna. El esquema contiene los nombres, el orden, los tipos y las reglas esperadas. shape solo informa la cantidad de filas y columnas.

Descarga sensores-aula.csv desde el panel de esta página y colócalo junto a tu cuaderno o archivo.

Pregunta. Antes de cargar el archivo, anota la forma esperada. Escribe los cinco nombres de columna. Asigna un tipo esperado a cada columna. Después, compara la predicción con el resultado.

Python 3 · ejemplo reproducible
import pandas as pd

tabla = pd.read_csv("sensores-aula.csv")

print(tabla.shape)
print(tabla.columns.tolist())
print(tabla.dtypes)
Salida esperada

La salida esperada está oculta.

info() reúne la misma auditoría y añade el conteo de valores no nulos. Su espaciado o memoria estimada pueden variar, así que tu salida no tiene que coincidir carácter por carácter con la mostrada abajo.

Pregunta. ¿Cuántos valores no nulos debería mostrar cada columna?

Python 3 · ejemplo reproducible
tabla.info()
Salida esperada

La salida esperada está oculta.

Índice e identificador

Al leer el CSV sin otra configuración, pandas crea un índice RangeIndex con etiquetas enteras. muestra_id sigue siendo una columna ordinaria con identificadores sintéticos de A-001 a A-008.

Pregunta. Si la tercera fila visible tiene etiqueta de índice 2, ¿qué valor debería devolver tabla.loc[2, "muestra_id"]?

Python 3 · ejemplo reproducible
print(tabla.index)
print(tabla.loc[2, "muestra_id"])
Salida esperada

La salida esperada está oculta.

Valores faltantes

isna() marca celdas faltantes. Sumar esos booleanos por columna produce un conteo verificable.

Pregunta. A partir de los valores no nulos que mostró info(), escribe qué conteo debería devolver isna().sum() en cada columna.

Python 3 · ejemplo reproducible
faltantes = tabla.isna().sum()

print(faltantes)
assert faltantes["humedad_pct"] == 1
assert faltantes.drop("humedad_pct").sum() == 0
Salida esperada

La salida esperada está oculta.

No compares un faltante con == pd.NA. Una igualdad con un valor desconocido puede seguir siendo desconocida. Para inspeccionar usa isna() o notna().

Después de leer una tabla, humedad_pct tiene un valor faltante. ¿Qué acción corresponde primero?

Puedes responder o seguir leyendo.

Selecciona una respuesta

Selección y filtrado

La solicitud usa un umbral inventado de 500 para practicar la selección. No es un límite real de seguridad. Primero conservamos solo el identificador y el valor que necesita el informe.

Pregunta. Anota la forma esperada de la selección antes de procesar.

Python 3 · ejemplo reproducible
co2 = tabla[["muestra_id", "co2_ppm"]]

print(co2.shape)
assert co2.shape == (8, 2)
Salida esperada

La salida esperada está oculta.

Para verificar que el filtro no cambie la fuente, guardamos una copia de referencia. Los identificadores exactos provienen del CSV completo, no solo de las tres filas mostradas al inicio.

Pregunta. Antes de procesar, decide qué debe permanecer cierto en tabla después de filtrar.

Python 3 · ejemplo reproducible
tabla_original = tabla.copy(deep=True)
co2_alto = tabla.loc[
    tabla["co2_ppm"] >= 500,
    ["muestra_id", "co2_ppm"],
]

print(co2_alto)
assert co2_alto["muestra_id"].tolist() == ["A-005", "A-007"]
assert tabla.shape == (8, 5)
pd.testing.assert_frame_equal(tabla, tabla_original)
Salida esperada

La salida esperada está oculta.

Mesa de prueba

Evidencia acumulada de la inspección

Recorre forma, tipos, faltantes y filtro. Antes de cambiar de estado, anticipa qué hallazgo añadirá a la nota. La tabla muestra cuatro de ocho filas y el color no contiene información necesaria.

Cuatro de las ocho filas del CSV sintético
muestra_idtemperatura_chumedad_pctco2_ppmzona
A-00122.461420norte
A-00321.8valor faltante415sur
A-00524.655510centro
A-00720.570530sur
Vista
Forma
Resultado
(8, 5)
Qué significa
8 observaciones y 5 columnas en el archivo completo.

Forma. Resultado: (8, 5). Significado: 8 observaciones y 5 columnas en el archivo completo.

Agregación por grupos

groupby("zona") reúne las filas que comparten el mismo valor de zona. Después, una función de agregación resume cada grupo en un solo valor. Las claves del grupo pasan a ser el índice del resultado.

Pregunta. Cuenta las zonas distintas del CSV. ¿Cuántos valores tendrá el resultado y en qué orden aparecerán las zonas?

Python 3 · ejemplo reproducible
co2_zona = tabla.groupby("zona")["co2_ppm"].mean()

print(co2_zona.round(1))
Salida esperada

La salida esperada está oculta.

agg acepta varias funciones. Con una lista sobre una columna, como groupby("zona")["co2_ppm"].agg(["min", "max"]), cada columna del resultado toma el nombre de la función. Con pares nombre=(columna, función), tú eliges el nombre de cada columna y puedes resumir columnas distintas.

Pregunta. Anota la forma de resumen. ¿Con cuántos valores de humedad se calcula la media de la zona sur?

Python 3 · ejemplo reproducible
resumen = tabla.groupby("zona").agg(
    co2_medio=("co2_ppm", "mean"),
    co2_max=("co2_ppm", "max"),
    humedad_media=("humedad_pct", "mean"),
)

print(resumen.round(1))
Salida esperada

La salida esperada está oculta.

size() cuenta las filas de cada grupo. count() cuenta los valores no nulos de una columna dentro de cada grupo.

Pregunta. Para cada zona, escribe el número de filas y el número de valores de humedad no nulos. ¿Cuánto sumará la columna filas?

Python 3 · ejemplo reproducible
por_zona = tabla.groupby("zona")
conteos = pd.DataFrame(
    {
        "filas": por_zona.size(),
        "humedad_no_nulos": por_zona["humedad_pct"].count(),
    }
)

print(conteos)
assert conteos["filas"].sum() == len(tabla)
Salida esperada

La salida esperada está oculta.

Combinación de tablas

El edificio y el tipo de ventilación de cada zona viven en otra tabla, con una fila por zona. merge une las dos tablas por la columna zona. Con how="left" conserva todas las filas de tabla. Con how="inner" conserva solo las filas cuya clave aparece en ambas tablas.

Pregunta. La tabla zonas no incluye centro. Anota la forma de izquierda y de interna. ¿Qué valor de edificio tendrán A-004 y A-005 en izquierda?

Python 3 · ejemplo reproducible
zonas = pd.DataFrame(
    {
        "zona": ["norte", "sur"],
        "edificio": ["B", "C"],
        "ventilacion": ["ventana", "mecánica"],
    }
)

izquierda = tabla.merge(zonas, on="zona", how="left")
interna = tabla.merge(zonas, on="zona", how="inner")

print(izquierda.shape)
print(interna.shape)
print(izquierda[["muestra_id", "zona", "edificio", "ventilacion"]])
Salida esperada

La salida esperada está oculta.

Cada zona debería aparecer una sola vez en la tabla derecha. Si una zona se repite, cada muestra de esa zona se une con todas sus copias.

Pregunta. En zonas_repetidas, sur aparece dos veces. ¿Cuántas filas tendrá repetida?

Python 3 · ejemplo reproducible
zonas_repetidas = pd.DataFrame(
    {
        "zona": ["norte", "sur", "sur"],
        "edificio": ["B", "C", "D"],
        "ventilacion": ["ventana", "mecánica", "ventana"],
    }
)

repetida = tabla.merge(zonas_repetidas, on="zona", how="left")
print(repetida.shape)
print(repetida["muestra_id"].duplicated().sum())
print(repetida.loc[repetida["zona"] == "sur", ["muestra_id", "edificio"]])
Salida esperada

La salida esperada está oculta.

validate="many_to_one" comprueba la regla de la tabla derecha: muchas filas de tabla pueden compartir zona, pero cada zona debe tener una sola fila en la tabla derecha.

Pregunta. ¿Qué hará el mismo merge con validate="many_to_one"?

Python 3 · ejemplo reproducible
try:
    tabla.merge(
        zonas_repetidas, on="zona", how="left", validate="many_to_one"
    )
except pd.errors.MergeError as error:
    print(error)
Salida esperada

La salida esperada está oculta.

indicator=True agrega la columna _merge, que registra de qué tabla proviene cada fila.

Pregunta. La tabla zonas_este agrega la zona este, que no aparece en tabla. ¿Qué categorías mostrará _merge y cuánto valdrá right_only?

Mostrar códigoOcultar códigoPython
Python 3 · ejemplo reproducible
zonas_este = pd.DataFrame(
    {
        "zona": ["norte", "sur", "este"],
        "edificio": ["B", "C", "E"],
        "ventilacion": ["ventana", "mecánica", "ventana"],
    }
)
Python 3 · ejemplo reproducible
revision = tabla.merge(
    zonas_este,
    on="zona",
    how="left",
    validate="many_to_one",
    indicator=True,
)

print(revision["_merge"].value_counts())
print(revision.loc[revision["_merge"] == "left_only", ["muestra_id", "zona"]])
assert len(revision) == len(tabla)
pd.testing.assert_frame_equal(tabla, tabla_original)
Salida esperada

La salida esperada está oculta.

Práctica

Traza guiada

Antes de ejecutar, predice forma, tipos generales y faltantes de esta tabla pequeña:

Python 3 · ejemplo reproducible
nueva = pd.DataFrame(
    {
        "muestra": ["B-01", "B-02", "B-03"],
        "valor": [2.5, None, 4.0],
        "grupo": ["x", "x", "y"],
    }
)

Comprueba con shape, dtypes e isna().sum(). Tu nota debe explicar por qué valor sigue siendo numérico aunque contenga None. Si no coincide, revisa primero los valores observados. No repares la ausencia.

Condición alternativa

Usa el CSV completo y cambia la condición a co2_ppm >= 490. Conserva muestra_id, co2_ppm y zona. Antes de procesar, predice la forma del subconjunto y la de tabla. Después, comprueba los identificadores con los datos completos. Repite la comparación con una copia de referencia.

Nota de inspección propia

Escribe un informe local siguiendo el mismo orden de la investigación:

  1. forma y columnas
  2. tipos generales y valores no nulos
  3. diferencia entre índice y muestra_id
  4. faltantes por columna
  5. un subconjunto filtrado y prueba de que la fuente no cambió

Reto opcional

tabla["muestra_id"].duplicated().sum() cuenta identificadores repetidos: un resultado 0 significa que ninguno aparece más de una vez. Si creas completas = tabla[tabla["humedad_pct"].notna()], descríbela como una tabla derivada y explica qué información perderías si la confundieras con una reparación.

Errores que revisar

  • Si la tabla parece correcta pero los tipos no, revisa columns, dtypes e info(). No uses la apariencia para comprobar los tipos.
  • Si aparece FileNotFoundError, comprueba la carpeta activa y el nombre exacto del CSV.
  • Si una ausencia parece cero, vuelve al archivo: una celda vacía registra falta de valor, no una medición de 0.
  • Si tabla cambió, revisa qué variable recibió la asignación y compárala con una copia para localizar la diferencia.
  • Si una combinación devuelve más filas que la tabla izquierda, busca claves repetidas en la tabla derecha con duplicated() y repite el merge con validate="many_to_one".

Cierre

Enumera los cuatro hallazgos en el orden en que los verificarías al recibir otro CSV.

Lista de comprobación

  • ¿Separaste forma, esquema e identidad de fila?
  • ¿Registraste el faltante sin decidir una reparación?
  • ¿Demostraste que el subconjunto no cambió tabla?
  • ¿Comprobaste que la combinación conserva una fila por muestra y localizaste las filas sin pareja?

Documentación oficial

Cargando progreso…