Saltar al contenido

Indice / pandas: calidad y limpieza / 5.4

Valores ausentes

Un dato que falta no es un cero, no es un texto vacío y no es un error. Es una tercera cosa, y casi todo lo que hagas después depende de que la trates como tal.

Para qué sirve

Ningún archivo real viene completo. Alguien no cargó la edad, el sistema exportó un guion donde no tenía dato, el formulario dejó pasar un campo en blanco. Si no decidís qué hacer con esos huecos, pandas decide por vos: unas funciones los saltean en silencio, otras te devuelven un error, y otras te dan un número que parece razonable y está mal.

Esta lección es sobre las dos preguntas que hay que responder, en este orden: cuántos ausentes hay de verdad y qué hacés con ellos. La primera es más difícil de lo que parece: en el archivo con el que vamos a trabajar, la respuesta obvia se equivoca por más del doble.

ELI5Explicado facil

Imaginate una lista de asistencia donde algunos casilleros quedaron en blanco. No sabés si esa persona vino o no: el casillero vacío no es un «no», es un «no sé».

Cuando abrís un archivo con Python, esos huecos también existen. Esta lección enseña dos cosas: contar cuántos huecos hay de verdad (spoiler: son más de los que parecen) y decidir qué hacer con ellos.

La idea: tres cosas que parecen la misma

Pensá en una planilla de asistencia en papel. Una celda con 0 significa que el alumno vino y sacó cero. Una celda vacía significa que nadie anotó nada: no sabemos si vino. Son dos hechos distintos del mundo, y si los mezclás, el promedio de la clase te va a mentir.

Tres valores que se confunden todo el tiempo
ValorQué significaCuenta en un promedio
0Sabemos que es ceroSí, y lo baja
"" (texto vacío)Hay un dato: es la cadena vacíaNo es número; rompe o contamina
NaNNo sabemosNo: se saltea

NaN quiere decir Not a Number, y en pandas representa «acá no hay dato». Tiene una propiedad que sorprende a todo el mundo la primera vez: NaN no es igual a sí mismo. Es coherente con lo que significa - si no sé cuánto vale ninguno de los dos, no puedo afirmar que sean iguales - pero tiene una consecuencia práctica inmediata: buscar los ausentes con == no encuentra ninguno.

ELI5Explicado facil

Hay tres cosas que parecen iguales y no lo son:

  • 0 = «medí y dio cero». Un dato.
  • texto vacío = alguien escribió nada. Sigue siendo texto.
  • NaN = «no sé». No hay dato.

Y lo más raro: dos «no sé» no son iguales entre sí. Si te pregunto cuánto miden dos personas que no conocés, no podés decirme que miden lo mismo. Por eso preguntar «¿este valor es igual a NaN?» siempre da que no, y hay que usar .isna(), que pregunta «¿acá falta el dato?».

El ejemplo mínimo

EjemploQue es un ausente, y por que no se lo busca con ==
import numpy as np
import pandas as pd

notas = pd.Series([7.5, np.nan, 9.0, np.nan], name="nota")
print(notas)

print()
print("NaN == NaN  ->", np.nan == np.nan)
print("encontrados con == :", (notas == np.nan).sum())
print("encontrados con .isna() :", notas.isna().sum())

print()
print("La media ignora los ausentes en silencio:")
print("  suma  =", notas.sum())
print("  media =", notas.mean(), " (suma / 2, no / 4)")
0    7.5
1    NaN
2    9.0
3    NaN
Name: nota, dtype: float64

NaN == NaN  -> False
encontrados con == : 0
encontrados con .isna() : 2

La media ignora los ausentes en silencio:
  suma  = 16.5
  media = 8.25  (suma / 2, no / 4)
Lo importante de este ejemplo

.sum() y .mean() ignoran los ausentes sin avisar. La media dio 8.25, que es 16.5 / 2 y no 16.5 / 4. No está mal: es la media de lo que sabemos. Pero si vos creías que era la media de las cuatro notas, tenés un número equivocado en las manos y ninguna señal de que lo esté.

Sobre datos de verdad

Vamos a trabajar con un archivo de clientes que ya viene sucio a propósito: clientes_crudos.csv (8.2 kB). Tiene 120 filas y la columna que nos interesa es edad.

Primer intento: leer y confiar

EjemploLeer el archivo tal cual: la columna edad no queda numerica
import pandas as pd

clientes = pd.read_csv("clientes_crudos.csv")

print(clientes[["id", "nombre", "edad"]].head(4))

print()
print("dtype de edad      :", clientes["edad"].dtype)
print("ausentes detectados:", clientes["edad"].isna().sum())

sospechosos = sorted({v for v in clientes["edad"].dropna().unique()
                      if not v.lstrip("-").isdigit()})
print("valores no numericos:", sospechosos)

print()
try:
    print(clientes["edad"].mean())
except TypeError as error:
    print("clientes['edad'].mean() ->", type(error).__name__)
    print(" ", error)
      id            nombre edad
0  C0001          José Paz   62
1  C0002        Lucía Peña   48
2  C0003  Santiago Herrera   40
3  C0004    José Rodríguez   67

dtype de edad      : str
ausentes detectados: 3
valores no numericos: ['-', 'treinta']

clientes['edad'].mean() -> TypeError
  Cannot perform reduction 'mean' with string dtype

Dos cosas para mirar acá. La primera: el dtype es str, no un número. La segunda: isna() encontró 3 ausentes. Esa cifra es la que vas a repetir en tu informe si no seguís mirando, y es falsa.

Si venís de tutoriales o de buscar en internet

Vas a ver por todos lados que una columna de texto tiene dtype object. Desde pandas 3.0 el tipo por defecto del texto es str. Es el mismo concepto y no cambia nada de lo que hacemos acá, pero conviene que no te desconcierte la diferencia.

Segundo intento: declarar los tokens que faltan

Cuando read_csv lee una celda, la compara contra una lista fija de cadenas que considera «ausente». En esa lista están NaN, N/A, null, NA y la celda vacía, entre otras. El guion no está. Y este archivo usa el guion como marca de dato faltante.

EjemploDeclarar los tokens de ausente que pandas no reconoce solo
import pandas as pd

por_defecto = pd.read_csv("clientes_crudos.csv")
declarado = pd.read_csv("clientes_crudos.csv", na_values=["-"])

print("sin na_values :", por_defecto["edad"].isna().sum(), "ausentes")
print("con na_values :", declarado["edad"].isna().sum(), "ausentes")
print("             el guion aparecia",
      (por_defecto["edad"] == "-").sum(), "vez y pasaba como dato valido")

print()
print("dtype de edad :", declarado["edad"].dtype)
print("Sigue siendo texto: adentro de la columna todavia queda 'treinta'.")
print("Declarar los ausentes no alcanza para volver numerica una columna.")
sin na_values : 3 ausentes
con na_values : 4 ausentes
             el guion aparecia 1 vez y pasaba como dato valido

dtype de edad : str
Sigue siendo texto: adentro de la columna todavia queda 'treinta'.
Declarar los ausentes no alcanza para volver numerica una columna.

Un ausente más. Parece poco; es una fila que hasta recién estaba viajando por todo tu análisis disfrazada de dato válido. El punto general: los tokens de ausente son una convención de quien exportó el archivo, no una ley. Antes de confiar en isna(), mirá qué cadenas raras aparecen en la columna.

ELI5Explicado facil

pandas tiene una lista de palabras que reconoce como «acá no hay nada»: NaN, N/A, null y el casillero vacío. Si tu archivo usa otra cosa para decir lo mismo -como un guion- pandas no se da cuenta y lo trata como si fuera un dato normal.

Es como si en la lista de asistencia alguien hubiera puesto una rayita en vez de dejar el casillero vacío. Vos entendés que significa «no sé», pero la computadora no, salvo que se lo digas: eso es na_values=["-"].

Tercer intento: convertir a número

La columna sigue siendo texto porque adentro quedó "treinta". Y mientras sea texto, no podés hacerle ninguna cuenta. pd.to_numeric con errors="coerce" convierte lo que puede y manda a NaN lo que no.

EjemploConvertir a numero y recien ahi contar los ausentes de verdad
import pandas as pd

clientes = pd.read_csv("clientes_crudos.csv", na_values=["-"])
edad = pd.to_numeric(clientes["edad"], errors="coerce")

print("dtype    :", edad.dtype)
print("ausentes :", edad.isna().sum(), "de", len(edad))
print("minimo   :", edad.min(), " maximo:", edad.max())

print()
validas = edad[edad.between(18, 100)]
print("dentro del dominio 18-100 :", len(validas))
print("fuera de rango            :", int(((edad < 18) | (edad > 100)).sum()))
print("mediana de las validas    :", validas.median())

print()
print("Los 3 ausentes nuevos son los 'treinta': errors='coerce' los convirtio")
print("en NaN. Sin coerce, to_numeric habria cortado con ValueError.")
dtype    : float64
ausentes : 7 de 120
minimo   : -5.0  maximo: 150.0

dentro del dominio 18-100 : 95
fuera de rango            : 18
mediana de las validas    : 46.0

Los 3 ausentes nuevos son los 'treinta': errors='coerce' los convirtio
en NaN. Sin coerce, to_numeric habria cortado con ValueError.

Recién ahora el número es real: 7 ausentes de 120, no 3. La diferencia entre 3 y 7 no es un detalle; es más del doble, y aparece únicamente si convertís antes de contar.

El orden importa

Primero declarás los tokens de ausente (na_values), después convertís el tipo (to_numeric), y recién ahí contás (isna()). Contar antes de convertir es el error más común de esta unidad, y no da ningún síntoma: te devuelve un número, simplemente es el número equivocado.

ELI5Explicado facil

Si en una columna de edades hay un valor que dice treinta escrito con letras, la computadora no puede sumar nada: mientras haya una sola palabra metida ahí, trata a toda la columna como texto.

to_numeric con errors="coerce" significa: «convertí a número lo que puedas, y lo que no se pueda dejalo como hueco». Después de eso la columna sí es numérica y recién ahí el conteo de huecos es el de verdad.

El orden es siempre el mismo: avisar cuáles son los huecos → convertir a número → contar. Contar primero da un número que parece bien y está mal.

Lo mismo, paso a paso

Seguí seis celdas reales del archivo mientras pasan por las tres etapas. Cada paso dice qué acaba de cambiar y por qué.

Paso a pasoDe texto del archivo a columna numerica, paso a paso
  1. El archivo es texto. Todas las celdas de la columna edad son cadenas, incluso las que parecen numeros.
    clientes = pd.read_csv("clientes_crudos.csv")
  2. read_csv reconoce solos algunos tokens de ausente: NaN, N/A, null, NA y la celda vacia. Esos pasan a NaN sin que le pidas nada.
    clientes = pd.read_csv("clientes_crudos.csv")
    # N/A y null estan en la lista por defecto de pandas
  3. Pero el guion NO esta en esa lista. Queda como un dato valido mas, y nadie avisa nada.
    clientes["edad"].isna().sum()   # 3, y hay 7
  4. Con na_values lo declaramos nosotros, y recien ahi el guion se vuelve ausente.
    clientes = pd.read_csv("clientes_crudos.csv", na_values=["-"])
  5. La columna sigue siendo texto: mientras quede una cadena como treinta adentro, pandas no puede hacerla numerica.
    clientes["edad"].dtype   # str, todavia no se puede promediar
  6. to_numeric convierte lo que puede y, con errors="coerce", manda a NaN lo que no. Sin coerce cortaria con ValueError en treinta.
    edad = pd.to_numeric(clientes["edad"], errors="coerce")
  7. Ahora si se puede contar: 4 ausentes de 6 en esta muestra. Sobre el archivo entero son 7 de 120, no los 3 que parecian al principio.
    edad.isna().sum()   # 7 sobre las 120 filas

Dónde se rompe

TypeError: Cannot perform reduction 'mean' with string dtype

Causa: estás sacando el promedio de una columna que pandas todavía ve como texto, casi siempre porque quedó adentro un token raro.

Arreglo: pd.to_numeric(col, errors="coerce") antes de cualquier cuenta. Si querés saber qué la ensucia, mirá los valores únicos que no son dígitos.

ValueError: Unable to parse string "treinta" at position 30

Causa: to_numeric sin errors="coerce" corta en el primer valor que no puede convertir.

Arreglo: este error es útil. Te está diciendo exactamente qué valor y en qué posición. Corré primero sin coerce para enterarte de qué hay, y recién después poné coerce a propósito, sabiendo qué estás mandando a NaN.

IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer

Causa: astype(int) sobre una columna que tiene ausentes. El tipo int de NumPy no tiene forma de representar «no sé».

Arreglo: decidí primero qué hacés con los ausentes. Si querés enteros conservando los huecos, usá astype("Int64") con I mayúscula, que sí los admite.

(sin error) df[df.edad == None] devuelve 0 filas

Causa: los ausentes no se comparan con ==. Ni con None, ni con np.nan, ni con "NaN".

Arreglo: df[df.edad.isna()]. Es el único camino, y no falla nunca. Este es el peor de los cuatro casos porque no se queja: te devuelve una tabla vacía y te deja creer que no hay ausentes.

(sin error) dropna() borra más filas de las que esperabas

Causa: sin subset, dropna() elimina toda fila que tenga un ausente en cualquier columna. En este archivo eso deja 112 filas; filtrando solo por edad quedan 116.

Arreglo: decí siempre qué columnas te importan: dropna(subset=["edad"]).

Acá hay que elegir

Ya sabés cuántos ausentes hay. Ahora hay que decidir, y no existe una respuesta correcta universal: existe una decisión que tenés que tomar y dejar por escrito. Son tres.

ELI5Explicado facil

Te falta la edad de siete personas. Tenés tres caminos, y ninguno es «el correcto»:

  • Descartar: borrás esas siete fichas. Simple, pero también perdés el nombre, la ciudad y todo lo demás que sí tenían.
  • Imputar: escribís un valor inventado, normalmente el del medio. Ojo: estás inventando datos. Anotá cuáles inventaste.
  • Conservar: dejás el hueco como está. Python ya sabe saltearlos cuando saca promedios.

Lo importante no es cuál elegís, sino que lo elijas a propósito y lo escribas.

EjemploLas tres politicas, con el numero que produce cada una
import pandas as pd

clientes = pd.read_csv("clientes_crudos.csv", na_values=["-"])
clientes["edad"] = pd.to_numeric(clientes["edad"], errors="coerce")
mediana = clientes.loc[clientes["edad"].between(18, 100), "edad"].median()

descartar = clientes.dropna(subset=["edad"])
imputar = clientes.assign(edad=clientes["edad"].fillna(mediana))
conservar = clientes.assign(edad_ausente=clientes["edad"].isna().astype("int64"))

print(f"{'politica':<11}{'filas':>7}{'media':>9}   {'que pasa con el ausente'}")
for nombre, tabla, nota in (
    ("descartar", descartar, "se pierde la fila entera"),
    ("imputar", imputar, f"pasa a valer {mediana:.0f} y se vuelve invisible"),
    ("conservar", conservar, "queda, marcado con una bandera"),
):
    print(f"{nombre:<11}{len(tabla):>7}{tabla['edad'].mean():>9.2f}   {nota}")

print()
print("Descartar y conservar dan la MISMA media: .mean() ya salteaba los NaN.")
print("Imputar es la unica que la mueve, porque inventa 7 valores.")
print()
print("Ojo: esa media sigue contaminada por las edades imposibles (-5, 150).")
print("Eso no es un problema de ausentes sino de rango, y se trata en 5.6.")
politica     filas    media   que pasa con el ausente
descartar      113    46.50   se pierde la fila entera
imputar        120    46.47   pasa a valer 46 y se vuelve invisible
conservar      120    46.50   queda, marcado con una bandera

Descartar y conservar dan la MISMA media: .mean() ya salteaba los NaN.
Imputar es la unica que la mueve, porque inventa 7 valores.

Ojo: esa media sigue contaminada por las edades imposibles (-5, 150).
Eso no es un problema de ausentes sino de rango, y se trata en 5.6.
Cómo elegir
  • Descartar cuando la fila sin ese dato no sirve para nada de lo que vas a hacer. Es honesto y simple, pero perdés todo lo demás que esa fila sí tenía.
  • Imputar cuando necesitás sí o sí un valor en cada fila (por ejemplo, para un modelo que no tolera huecos). Estás inventando datos: dejá siempre una columna que diga cuáles inventaste.
  • Conservar cuando estás analizando, que es casi siempre. pandas ya sabe saltear NaN; no necesitás rellenarlo para calcular una media.

La regla que salva: si imputás, que se note. Una columna edad_imputada con 0 y 1 cuesta una línea y evita que dentro de tres meses alguien lea 46 años y crea que eso lo dijo el cliente.

Si imputás: ¿con qué?

Decidir imputar es solo la mitad de la decisión. Falta elegir el valor, y ahí hay cuatro candidatos habituales. Tres son variantes de fillna y el cuarto es otra función.

Los cuatro rellenos que vas a probar abajo
RellenoQué escribe en el huecoCuándo tiene sentido
fillna(mediana) El valor del medio de la columna La opción por defecto: no la mueven los valores extremos
fillna(media) El promedio de la columna Solo si no hay atípicos, porque el promedio sí se deja arrastrar
fillna(0) Un cero Cuando el hueco de verdad significa cero, como «ventas: ninguna»
ffill() El último valor no ausente que apareció más arriba Series de tiempo ordenadas, donde el dato «sigue vigente»
ffill quiere decir forward fill, «rellenar hacia adelante»

Recorre la columna de arriba abajo y, cada vez que encuentra un hueco, copia el último valor que vio. Si la columna es [10, NaN, NaN, 40], queda [10, 10, 10, 40]: el 10 se arrastra hacia abajo hasta que aparece otro dato. Existe también bfill (backward fill), que hace lo mismo mirando hacia arriba.

Dos cosas que sorprenden la primera vez: si el primer valor de la columna es un ausente, se queda ausente, porque no hay nada anterior para copiar; y el resultado depende del orden de las filas, así que ordenar la tabla distinto cambia los datos que inventaste.

ffill tiene sentido cuando la fila de arriba y la de abajo están relacionadas: mediciones de un sensor cada hora, la cotización de una moneda día por día, el stock de un producto a lo largo del tiempo. En una tabla de clientes no lo tiene: la edad del cliente C0028 no dice nada sobre la del C0029. Está en la lista de abajo justamente para que veas que produce un resultado -no falla, no avisa- y que aun así es la respuesta equivocada.

ELI5Explicado facil

ffill es «copiá el de arriba». Recorre la columna de arriba abajo y, cada vez que encuentra un hueco, pone el último valor que vio.

Sirve cuando las filas están en orden de tiempo y el dato «sigue valiendo»: si el lunes la temperatura fue 20 grados y el martes no se midió, copiar 20 es razonable.

No sirve en una lista de clientes: copiar la edad del cliente anterior es como decir que si no sabés la edad de tu compañero de banco, le ponés la tuya. Python lo hace igual, sin quejarse. Ese es el peligro.

Probá los cuatro y mirá dos cosas: qué valor termina en cada hueco, y qué le pasa al desvío estándar de la columna entera.

Proba cambiarImputar no es gratis: mira que le hace cada relleno a la columna
import pandas as pd

try:
    RELLENO
except NameError:
    RELLENO = "mediana"

clientes = pd.read_csv("clientes_crudos.csv", na_values=["-"])
edad = pd.to_numeric(clientes["edad"], errors="coerce")
faltan = edad.isna()

# El texto tal cual venia en el archivo, para ver que token era cada ausente.
crudo = pd.read_csv("clientes_crudos.csv", dtype=str, keep_default_na=False)

validas = edad[edad.between(18, 100)]
if RELLENO == "mediana":
    rellenada = edad.fillna(validas.median())
elif RELLENO == "media":
    rellenada = edad.fillna(round(validas.mean(), 1))
elif RELLENO == "cero":
    rellenada = edad.fillna(0)
elif RELLENO == "ffill":
    rellenada = edad.ffill()
else:
    raise ValueError(f"relleno desconocido: {RELLENO}")

print(f"RELLENO = {RELLENO!r}   ({faltan.sum()} valores imputados de {len(edad)})")
print()

comparacion = pd.DataFrame({
    "id": clientes.loc[faltan, "id"],
    "en_el_archivo": crudo.loc[faltan, "edad"].replace("", "(vacio)"),
    "edad_imputada": rellenada[faltan],
})
print(comparacion.to_string(index=False))

print()
print(f"media antes  : {edad.mean():.2f}")
print(f"media despues: {rellenada.mean():.2f}")
print(f"desvio antes  : {edad.std():.2f}")
print(f"desvio despues: {rellenada.std():.2f}")

RELLENO = "mediana"

RELLENO = 'mediana'   (7 valores imputados de 120)

   id en_el_archivo  edad_imputada
C0029           N/A           46.0
C0031       treinta           46.0
C0058       (vacio)           46.0
C0062       treinta           46.0
C0087             -           46.0
C0093       treinta           46.0
C0116          null           46.0

media antes  : 46.50
media despues: 46.47
desvio antes  : 28.23
desvio despues: 27.39

RELLENO = "media"

RELLENO = 'media'   (7 valores imputados de 120)

   id en_el_archivo  edad_imputada
C0029           N/A           46.3
C0031       treinta           46.3
C0058       (vacio)           46.3
C0062       treinta           46.3
C0087             -           46.3
C0093       treinta           46.3
C0116          null           46.3

media antes  : 46.50
media despues: 46.48
desvio antes  : 28.23
desvio despues: 27.39

RELLENO = "cero"

RELLENO = 'cero'   (7 valores imputados de 120)

   id en_el_archivo  edad_imputada
C0029           N/A            0.0
C0031       treinta            0.0
C0058       (vacio)            0.0
C0062       treinta            0.0
C0087             -            0.0
C0093       treinta            0.0
C0116          null            0.0

media antes  : 46.50
media despues: 43.78
desvio antes  : 28.23
desvio despues: 29.50

RELLENO = "ffill"

RELLENO = 'ffill'   (7 valores imputados de 120)

   id en_el_archivo  edad_imputada
C0029           N/A           44.0
C0031       treinta           51.0
C0058       (vacio)          128.0
C0062       treinta           65.0
C0087             -           36.0
C0093       treinta           -5.0
C0116          null           -5.0

media antes  : 46.50
media despues: 46.40
desvio antes  : 28.23
desvio despues: 29.23

Ninguno de los cuatro es neutro. Con la mediana el desvío baja de 28,2 a 27,4: metiste siete veces el mismo valor justo en el centro y la columna quedó artificialmente más concentrada de lo que era en realidad. La media da casi lo mismo acá solo porque en este archivo el promedio (46,5) y la mediana (46,0) casi coinciden; con una columna torcida por atípicos se separarían mucho. Con cero es peor: además de bajar la media, agregás siete edades imposibles que la lección 5.6 va a marcar como fuera de rango.

Y mirá lo que hizo ffill

Cada hueco copió la edad del cliente de la fila de arriba, que no tiene ninguna relación con él. Pero además pasó algo peor: como copia el último valor visto sin fijarse si es razonable, arrastró un 128 y dos veces un -5. Es decir que no solo inventó datos: propagó los errores que la columna ya tenía, y ahora hay tres filas más con edades imposibles que antes estaban limpias.

Nada falló, nada avisó, y el archivo resultante tiene más basura que el original. Este es el argumento entero de la lección en un solo ejemplo: la función corrió bien y el resultado está mal.

Recetario

Lo mínimo para trabajar con ausentes
ExpresiónQué devuelveCuándo
read_csv(f, na_values=["-"])DataFrameCuando el archivo usa tokens propios de ausente
pd.to_numeric(s, errors="coerce")Series numéricaSiempre, antes de contar o calcular
s.isna()Series de boolPara encontrar los ausentes
s.isna().sum()EnteroPara contarlos
s.notna()Series de boolLo contrario; útil como máscara
df.isna().sum()Series por columnaDiagnóstico rápido de toda la tabla
s.fillna(v)SeriesImputar con un valor fijo
s.fillna(s.median())SeriesImputar con la mediana
s.ffill()Seriesforward fill: copia el último valor visto hacia abajo. Solo en datos ordenados en el tiempo
s.bfill()SeriesLo mismo pero hacia arriba: copia el siguiente valor
df.dropna(subset=["c"])DataFrameDescartar por una columna concreta
s.astype("Int64")Series entera nullableEnteros que admiten ausentes

Ponete a prueba

1. Si notas es una Series de 4 valores con 2 ausentes, ¿qué devuelve notas.isna()?Nivel 1 · Recordar
2. ¿Cuál de estas cadenas NO la reconoce read_csv como valor ausente si no se lo pedís?Nivel 1 · Recordar
3. notas = pd.Series([7.5, np.nan, 9.0, np.nan])
¿Qué imprime print(notas.mean())?Nivel 2 · Predecir la salida
4. Leés el archivo con pd.read_csv("clientes_crudos.csv", na_values=["-"]). La columna edad todavía contiene la cadena "treinta". ¿Cuál es su dtype?Nivel 2 · Predecir la salida
5. Querés quedarte con las filas sin edad. Las cuatro líneas corren sin error. ¿Cuál hace lo que pedís?Nivel 3 · Elegir entre dos casi correctas
6. Querés descartar las filas sin edad, pero conservar las que tienen la edad y les falta el correo. ¿Cuál usás?Nivel 3 · Elegir entre dos casi correctas
7. Este código corre sin errores e informa 3 ausentes. El archivo tiene 7. ¿Dónde está el problema?
clientes = pd.read_csv("clientes_crudos.csv")
faltan = clientes["edad"].isna().sum()
print(f"ausentes: {faltan}")
edad = pd.to_numeric(clientes["edad"], errors="coerce")
media = edad.mean()
Nivel 4 · Encontrar el defecto

Prácticas de esta lección