Módulo 2 · Datos
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_id | temperatura_c | humedad_pct | co2_ppm | zona |
|---|---|---|---|---|
| A-001 | 22.4 | 61 | 420 | norte |
| A-002 | 23.1 | 58 | 435 | norte |
| A-003 | 21.8 | celda vacía | 415 | sur |
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.
import pandas as pd
tabla = pd.read_csv("sensores-aula.csv")
print(tabla.shape)
print(tabla.columns.tolist())
print(tabla.dtypes)
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?
tabla.info()
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"]?
print(tabla.index)
print(tabla.loc[2, "muestra_id"])
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.
faltantes = tabla.isna().sum()
print(faltantes)
assert faltantes["humedad_pct"] == 1
assert faltantes.drop("humedad_pct").sum() == 0
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().
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.
co2 = tabla[["muestra_id", "co2_ppm"]]
print(co2.shape)
assert co2.shape == (8, 2)
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.
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)
La salida esperada está oculta.
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.
| muestra_id | temperatura_c | humedad_pct | co2_ppm | zona |
|---|---|---|---|---|
| A-001 | 22.4 | 61 | 420 | norte |
| A-003 | 21.8 | valor faltante | 415 | sur |
| A-005 | 24.6 | 55 | 510 | centro |
| A-007 | 20.5 | 70 | 530 | sur |
- 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?
co2_zona = tabla.groupby("zona")["co2_ppm"].mean()
print(co2_zona.round(1))
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?
resumen = tabla.groupby("zona").agg(
co2_medio=("co2_ppm", "mean"),
co2_max=("co2_ppm", "max"),
humedad_media=("humedad_pct", "mean"),
)
print(resumen.round(1))
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?
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)
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?
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"]])
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?
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"]])
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"?
try:
tabla.merge(
zonas_repetidas, on="zona", how="left", validate="many_to_one"
)
except pd.errors.MergeError as error:
print(error)
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ódigo
zonas_este = pd.DataFrame(
{
"zona": ["norte", "sur", "este"],
"edificio": ["B", "C", "E"],
"ventilacion": ["ventana", "mecánica", "ventana"],
}
)
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)
La salida esperada está oculta.
Práctica
Traza guiada
Antes de ejecutar, predice forma, tipos generales y faltantes de esta tabla pequeña:
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:
- forma y columnas
- tipos generales y valores no nulos
- diferencia entre índice y
muestra_id - faltantes por columna
- 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,dtypeseinfo(). 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
tablacambió, 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 elmergeconvalidate="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
- Qué datos maneja pandas: filas, columnas,
SeriesyDataFrame. - Leer y escribir datos tabulares:
read_csv, inspección, tipos einfo(). - Seleccionar subconjuntos: columnas, filtros y
notna(). - Trabajar con datos faltantes: semántica de faltantes e inspección.