Módulo 12 — Leer y escribir datos: CSV, JSON y Excel
El módulo 04 te enseñó a abrir archivos de texto con open(). Eso funciona para ficheros simples, pero en análisis de datos los formatos habituales son CSV, JSON y Excel. Pandas los lee y escribe en una sola línea cada uno. Este módulo cubre los tres y añade pathlib para gestionar rutas sin romperte la cabeza con barras y sistemas operativos.
CSV: el formato que más vas a ver
Un CSV es un archivo de texto donde cada línea es una fila y los valores se separan con comas (o punto y coma si el separador decimal es la coma, como en español).
import pandas as pd
df = pd.read_csv("ventas.csv")
print(df.head())
read_csv tiene muchos parámetros, pero estos son los que vas a necesitar:
df = pd.read_csv(
"ventas.csv",
sep=";", # separador (por defecto coma)
encoding="utf-8", # codificación (ponlo siempre para evitar problemas con tildes)
decimal=",", # si el decimal usa coma en lugar de punto
usecols=["fecha", "importe", "ciudad"], # leer solo esas columnas
parse_dates=["fecha"], # convertir esa columna a datetime al cargar
na_values=["N/A", "-", ""], # cadenas que se tratan como nulo
nrows=1000, # leer solo las primeras N filas (útil para archivos grandes)
)
No necesitas usarlos todos a la vez. Añade los que el archivo requiera.
Para exportar:
df.to_csv("resultado.csv", index=False, encoding="utf-8")
index=False evita que Pandas escriba la columna del índice (0, 1, 2…) como primera columna. Ponlo casi siempre.
JSON: datos que vienen de APIs o configuraciones
JSON es el formato de intercambio de datos en la web. Pandas puede leerlo cuando está orientado a registros (cada elemento es una fila) o cuando la API devuelve un objeto con una clave que contiene la lista de datos.
# JSON con una lista de objetos
df = pd.read_json("datos.json")
# Si el JSON tiene estructura anidada, mejor usar el módulo json y normalizar
import json
with open("respuesta.json", "r", encoding="utf-8") as f:
datos = json.load(f)
# Si los datos están bajo una clave:
df = pd.DataFrame(datos["resultados"])
# O con json_normalize para objetos anidados:
from pandas import json_normalize
df = json_normalize(datos["resultados"])
Para escribir:
df.to_json("salida.json", orient="records", force_ascii=False, indent=2)
orient="records" produce una lista de objetos, que es lo más útil para APIs. force_ascii=False mantiene las tildes.
Para trabajar con JSON sin Pandas (guardar configuración, logs, resultados intermedios):
import json
config = {"umbral": 0.75, "modo": "producción", "columnas": ["a", "b"]}
with open("config.json", "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=2)
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)
print(config["umbral"]) # 0.75
Excel: el formato que siempre aparece en los proyectos reales
Para leer Excel necesitas openpyxl (para .xlsx) instalado:
pip install openpyxl
df = pd.read_excel("informe.xlsx", sheet_name="Ventas")
Si no especificas sheet_name, lee la primera hoja. También puedes pasar un índice (0, 1…) o None para leer todas las hojas como un diccionario de DataFrames.
Los mismos parámetros útiles que en CSV funcionan aquí: usecols, nrows, na_values.
Para escribir:
df.to_excel("resultado.xlsx", sheet_name="Resumen", index=False)
Para escribir varios DataFrames en distintas hojas del mismo archivo:
with pd.ExcelWriter("informe_completo.xlsx", engine="openpyxl") as writer:
df_ventas.to_excel(writer, sheet_name="Ventas", index=False)
df_resumen.to_excel(writer, sheet_name="Resumen", index=False)
df_clientes.to_excel(writer, sheet_name="Clientes", index=False)
Rutas sin sorpresas: pathlib
Concatenar rutas con strings es frágil y depende del sistema operativo (/ en Linux/Mac, \ en Windows). pathlib.Path resuelve eso:
from pathlib import Path
directorio = Path("datos")
archivo = directorio / "ventas_2024.csv" # el operador / construye rutas
print(archivo) # datos/ventas_2024.csv
# Comprobar si existe antes de leer:
if archivo.exists():
df = pd.read_csv(archivo)
# Crear el directorio si no existe:
directorio.mkdir(parents=True, exist_ok=True)
# Listar todos los CSV de una carpeta:
for csv in Path("datos").glob("*.csv"):
print(csv.name)
# Información de la ruta:
print(archivo.stem) # ventas_2024 (nombre sin extensión)
print(archivo.suffix) # .csv
print(archivo.parent) # datos
Path funciona igual en Windows, Mac y Linux. Úsala siempre que trabajes con rutas en código que va a moverse entre máquinas.
Cuándo usar cada formato
| Situación | Formato |
|---|---|
| Intercambio de datos, tablas planas | CSV |
| APIs, configuración, datos anidados | JSON |
| Informes para clientes o compañeros | Excel |
| Pipeline de datos entre scripts Python | CSV o Parquet (avanzado) |
Ejercicios
12.1 — Crea a mano un DataFrame con 5 productos (nombre, precio, stock). Expórtalo a CSV sin índice. Luego léelo de nuevo y muestra las filas donde el stock es menor que 20.
12.2 — Dado el JSON siguiente guardado en un archivo productos.json, léelo con Pandas y calcula el precio medio por categoría:
[
{"nombre": "Teclado", "categoria": "Periféricos", "precio": 45},
{"nombre": "Ratón", "categoria": "Periféricos", "precio": 25},
{"nombre": "Monitor", "categoria": "Pantallas", "precio": 199},
{"nombre": "Webcam", "categoria": "Periféricos", "precio": 60},
{"nombre": "Tablet", "categoria": "Pantallas", "precio": 349}
]
12.3 — Descarga un CSV del INE o usa cualquier CSV público de más de 500 filas. Léelo pasando nrows=100 y usecols para quedarte solo con 3 columnas. Exporta esas 100 filas a Excel en una hoja llamada “Muestra”.
12.4 — Escribe un script que use pathlib para listar todos los archivos .csv en una carpeta llamada datos/ (créala si no existe). Para cada archivo, imprime su nombre y el número de filas que tiene.
12.5 — Lee el archivo de ventas de tu elección. Usa pd.ExcelWriter para exportarlo a un Excel con dos hojas: una con los datos completos y otra con un resumen (total de filas, media de precios, valor máximo).
12.6 — Crea un diccionario de configuración con al menos 4 parámetros (umbrales, nombres de columnas, rutas). Guárdalo como JSON con indentación. Luego cárgalo y úsalo para filtrar un DataFrame.
Soluciones
12.1
import pandas as pd
productos = pd.DataFrame({
"nombre": ["Teclado", "Ratón", "Monitor", "Webcam", "USB Hub"],
"precio": [45, 25, 199, 60, 15],
"stock": [30, 15, 8, 50, 12]
})
productos.to_csv("productos.csv", index=False, encoding="utf-8")
df = pd.read_csv("productos.csv")
print(df[df["stock"] < 20])
12.2
import json, pandas as pd
from pathlib import Path
datos = [
{"nombre": "Teclado", "categoria": "Periféricos", "precio": 45},
{"nombre": "Ratón", "categoria": "Periféricos", "precio": 25},
{"nombre": "Monitor", "categoria": "Pantallas", "precio": 199},
{"nombre": "Webcam", "categoria": "Periféricos", "precio": 60},
{"nombre": "Tablet", "categoria": "Pantallas", "precio": 349}
]
Path("productos.json").write_text(json.dumps(datos, ensure_ascii=False), encoding="utf-8")
df = pd.read_json("productos.json")
print(df.groupby("categoria")["precio"].mean())
# Pantallas 274.0
# Periféricos 43.3...
12.3
import pandas as pd
# Ejemplo con cualquier CSV público grande
df = pd.read_csv("datos_grandes.csv", nrows=100, usecols=["col1", "col2", "col3"])
df.to_excel("muestra.xlsx", sheet_name="Muestra", index=False)
print(df.shape) # (100, 3)
12.4
from pathlib import Path
import pandas as pd
carpeta = Path("datos")
carpeta.mkdir(parents=True, exist_ok=True)
for archivo in carpeta.glob("*.csv"):
df = pd.read_csv(archivo)
print(f"{archivo.name}: {len(df)} filas")
12.5
import pandas as pd
df = pd.read_csv("ventas.csv")
resumen = pd.DataFrame({
"total_filas": [len(df)],
"media_precio": [df["precio_unitario"].mean()],
"max_precio": [df["precio_unitario"].max()]
})
with pd.ExcelWriter("informe.xlsx", engine="openpyxl") as writer:
df.to_excel(writer, sheet_name="Datos", index=False)
resumen.to_excel(writer, sheet_name="Resumen", index=False)
12.6
import json, pandas as pd
from pathlib import Path
config = {
"columna_precio": "precio_unitario",
"columna_fecha": "fecha",
"umbral_minimo": 50,
"categorias_activas": ["Electrónica", "Periféricos"]
}
Path("config.json").write_text(
json.dumps(config, ensure_ascii=False, indent=2), encoding="utf-8"
)
with open("config.json", encoding="utf-8") as f:
cfg = json.load(f)
df = pd.read_csv("ventas.csv")
filtrado = df[df[cfg["columna_precio"]] >= cfg["umbral_minimo"]]
print(filtrado.shape)