Convertir hojas de cálculo entre formatos con Python
Los datos tabulares existen en múltiples formatos: Excel (.xlsx, .xls), CSV, ODS (LibreOffice), JSON, Parquet, HTML, Feather y más. Python con pandas y openpyxl hace estas conversiones triviales, incluyendo lotes, múltiples hojas y transformaciones en el proceso.
Instalación
pip install pandas openpyxl odfpy xlrd pyarrow fastparquet
# xlrd para .xls (Excel 97-2003)
# odfpy para ODS (OpenDocument)
# pyarrow para Parquet
Lectura universal con pandas
import pandas as pd
def leer_tabla(ruta, hoja=0, **kwargs):
"""Lee cualquier formato tabular según la extensión."""
ext = ruta.lower().rsplit('.', 1)[-1]
lectores = {
'xlsx': lambda r: pd.read_excel(r, sheet_name=hoja, **kwargs),
'xls': lambda r: pd.read_excel(r, sheet_name=hoja, engine='xlrd', **kwargs),
'ods': lambda r: pd.read_excel(r, sheet_name=hoja, engine='odf', **kwargs),
'csv': lambda r: pd.read_csv(r, **kwargs),
'tsv': lambda r: pd.read_csv(r, sep='\t', **kwargs),
'json': lambda r: pd.read_json(r, **kwargs),
'parquet': lambda r: pd.read_parquet(r, **kwargs),
'html': lambda r: pd.read_html(r, **kwargs)[0],
}
if ext not in lectores:
raise ValueError(f"Formato no soportado: .{ext}")
df = lectores[ext](ruta)
print(f"Leído: {ruta} → {df.shape[0]} filas × {df.shape[1]} columnas")
return df
df = leer_tabla("ventas.xlsx")
print(df.head())
Excel → CSV
import pandas as pd
from pathlib import Path
def excel_a_csv(ruta_xlsx, carpeta_salida=None, sep=',', encoding='utf-8-sig'):
"""
Convierte cada hoja de un Excel a un archivo CSV independiente.
utf-8-sig incluye BOM para compatibilidad con Excel en Windows.
"""
carpeta_salida = Path(carpeta_salida or Path(ruta_xlsx).parent)
carpeta_salida.mkdir(exist_ok=True)
nombre_base = Path(ruta_xlsx).stem
xl = pd.ExcelFile(ruta_xlsx)
archivos_generados = []
for nombre_hoja in xl.sheet_names:
df = xl.parse(nombre_hoja)
if df.empty:
print(f" (saltando hoja vacía: {nombre_hoja})")
continue
# Nombre limpio para el archivo
nombre_limpio = nombre_hoja.replace(' ', '_').replace('/', '-')
ruta_csv = carpeta_salida / f"{nombre_base}_{nombre_limpio}.csv"
df.to_csv(ruta_csv, index=False, sep=sep, encoding=encoding)
archivos_generados.append(ruta_csv)
print(f" ✓ {ruta_csv.name}: {len(df)} filas")
return archivos_generados
archivos = excel_a_csv("reporte_anual.xlsx", "csvs/")
CSV → Excel (con múltiples CSVs en un libro)
import pandas as pd
from pathlib import Path
def csvs_a_excel(lista_csv, ruta_salida, nombres_hoja=None):
"""
Combina varios archivos CSV en un único libro Excel,
cada uno como una hoja separada.
"""
with pd.ExcelWriter(ruta_salida, engine='openpyxl') as writer:
for i, ruta_csv in enumerate(lista_csv):
df = pd.read_csv(ruta_csv, encoding='utf-8-sig')
nombre_hoja = (nombres_hoja[i] if nombres_hoja and i < len(nombres_hoja)
else Path(ruta_csv).stem[:31]) # Excel: máx 31 chars
df.to_excel(writer, sheet_name=nombre_hoja, index=False)
print(f" ✓ Hoja '{nombre_hoja}': {len(df)} filas")
print(f"Excel generado: {ruta_salida}")
csvs_a_excel(
["enero.csv", "febrero.csv", "marzo.csv"],
"trimestre_q1.xlsx",
nombres_hoja=["Enero", "Febrero", "Marzo"]
)
Excel → ODS (LibreOffice)
import pandas as pd
def excel_a_ods(ruta_xlsx, ruta_ods):
"""Convierte Excel a formato ODS (OpenDocument Spreadsheet)."""
xl = pd.ExcelFile(ruta_xlsx)
with pd.ExcelWriter(ruta_ods, engine='odf') as writer:
for nombre_hoja in xl.sheet_names:
df = xl.parse(nombre_hoja)
if not df.empty:
df.to_excel(writer, sheet_name=nombre_hoja[:31], index=False)
print(f" Hoja '{nombre_hoja}': {len(df)} filas")
print(f"ODS guardado: {ruta_ods}")
excel_a_ods("datos.xlsx", "datos.ods")
Excel → JSON
import pandas as pd
import json
def excel_a_json(ruta_xlsx, ruta_json, orientacion='records',
hoja=0, indentar=2):
"""
orientacion:
- 'records': lista de diccionarios [{"col": val}, ...]
- 'split': {"columns": [...], "data": [[...]]}
- 'index': {0: {"col": val}, 1: ...}
- 'values': solo los datos como array 2D
"""
df = pd.read_excel(ruta_xlsx, sheet_name=hoja)
# Convertir tipos no serializables
df = df.where(df.notna(), None) # NaN → None
with open(ruta_json, 'w', encoding='utf-8') as f:
json.dump(
json.loads(df.to_json(orient=orientacion, force_ascii=False, date_format='iso')),
f, ensure_ascii=False, indent=indentar
)
print(f"JSON guardado: {ruta_json} ({len(df)} registros, orient='{orientacion}')")
excel_a_json("clientes.xlsx", "clientes.json", orientacion='records')
Salida (orient='records'):
[
{
"nombre": "Ana García",
"email": "ana@ejemplo.com",
"ciudad": "Madrid",
"ventas": 15000
},
...
]
Excel → Parquet (columnar, ideal para Big Data)
import pandas as pd
def excel_a_parquet(ruta_xlsx, ruta_parquet, hoja=0,
compresion='snappy', optimizar_tipos=True):
"""
Parquet es un formato columnar muy eficiente para grandes volúmenes.
compresion: 'snappy' (rápido), 'gzip' (más compacto), 'brotli'
"""
df = pd.read_excel(ruta_xlsx, sheet_name=hoja)
if optimizar_tipos:
# Optimizar uso de memoria infiriendo tipos más eficientes
for col in df.select_dtypes(include='object'):
try:
df[col] = pd.to_numeric(df[col])
except (ValueError, TypeError):
pass
for col in df.select_dtypes(include='float64'):
if df[col].dropna().apply(float.is_integer).all():
df[col] = df[col].astype('Int64') # nullable int
df.to_parquet(ruta_parquet, engine='pyarrow',
compression=compresion, index=False)
tam_xlsx = Path(ruta_xlsx).stat().st_size
tam_parquet = Path(ruta_parquet).stat().st_size
ahorro = (1 - tam_parquet / tam_xlsx) * 100
print(f"Parquet: {tam_xlsx/1024:.0f}KB → {tam_parquet/1024:.0f}KB (ahorro: {ahorro:.1f}%)")
excel_a_parquet("big_data.xlsx", "big_data.parquet")
Conversión en lote: cualquier formato → cualquier formato
import pandas as pd
from pathlib import Path
ESCRITORES = {
'csv': lambda df, p: df.to_csv(p, index=False, encoding='utf-8-sig'),
'xlsx': lambda df, p: df.to_excel(p, index=False, engine='openpyxl'),
'ods': lambda df, p: df.to_excel(p, index=False, engine='odf'),
'json': lambda df, p: df.to_json(p, orient='records', force_ascii=False, indent=2),
'parquet': lambda df, p: df.to_parquet(p, engine='pyarrow', index=False),
'html': lambda df, p: df.to_html(p, index=False, classes='table table-striped'),
'tsv': lambda df, p: df.to_csv(p, sep='\t', index=False),
}
LECTORES = {
'xlsx': lambda p: pd.read_excel(p, engine='openpyxl'),
'xls': lambda p: pd.read_excel(p, engine='xlrd'),
'ods': lambda p: pd.read_excel(p, engine='odf'),
'csv': lambda p: pd.read_csv(p),
'tsv': lambda p: pd.read_csv(p, sep='\t'),
'json': lambda p: pd.read_json(p),
'parquet': lambda p: pd.read_parquet(p, engine='pyarrow'),
}
def convertir_tabla(origen, destino_fmt, carpeta_salida=None):
origen = Path(origen)
ext_origen = origen.suffix.lstrip('.').lower()
if ext_origen not in LECTORES:
raise ValueError(f"Formato de entrada no soportado: .{ext_origen}")
if destino_fmt not in ESCRITORES:
raise ValueError(f"Formato de salida no soportado: .{destino_fmt}")
df = LECTORES[ext_origen](str(origen))
carpeta = Path(carpeta_salida) if carpeta_salida else origen.parent
carpeta.mkdir(exist_ok=True)
destino = carpeta / f"{origen.stem}.{destino_fmt}"
ESCRITORES[destino_fmt](df, str(destino))
print(f"Convertido: {origen.name} → {destino.name} ({len(df)} filas)")
return str(destino)
def batch_convertir(carpeta, fmt_origen, fmt_destino, carpeta_salida=None):
carpeta = Path(carpeta)
archivos = list(carpeta.glob(f"*.{fmt_origen}"))
print(f"Convirtiendo {len(archivos)} archivos .{fmt_origen} → .{fmt_destino}")
for archivo in archivos:
try:
convertir_tabla(archivo, fmt_destino, carpeta_salida)
except Exception as e:
print(f" ✗ {archivo.name}: {e}")
# Convertir todos los CSV de una carpeta a Excel
batch_convertir("datos/csvs/", "csv", "xlsx", "datos/excel/")
# Convertir todos los Excel a Parquet
batch_convertir("datos/excel/", "xlsx", "parquet", "datos/parquet/")
Combinar y transformar al convertir
import pandas as pd
from pathlib import Path
def fusionar_csv_y_exportar_excel(lista_csv, ruta_salida,
columna_origen=True):
"""Combina múltiples CSVs añadiendo una columna que indica el origen."""
frames = []
for ruta_csv in lista_csv:
df = pd.read_csv(ruta_csv)
if columna_origen:
df['origen'] = Path(ruta_csv).stem
frames.append(df)
df_total = pd.concat(frames, ignore_index=True)
# Exportar con formato automático por tipo de columna
with pd.ExcelWriter(ruta_salida, engine='openpyxl') as writer:
df_total.to_excel(writer, sheet_name='Datos', index=False)
# Aplicar formato de número a columnas numéricas
ws = writer.sheets['Datos']
for col in df_total.select_dtypes(include='number').columns:
col_idx = df_total.columns.get_loc(col) + 1
for row in range(2, len(df_total) + 2):
ws.cell(row=row, column=col_idx).number_format = '#,##0.00'
print(f"Fusionados {len(lista_csv)} CSVs → {len(df_total)} filas → {ruta_salida}")
fusionar_csv_y_exportar_excel(
["ventas_norte.csv", "ventas_sur.csv", "ventas_este.csv"],
"ventas_total.xlsx"
)
Recurso adicional
Para convertir archivos Excel, CSV, ODS y otros formatos de hoja de cálculo sin necesidad de programar, usa KaijuConverter — gratis y sin registro.
Conversiones relacionadas
Conversiones frecuentes del catálogo: