Hace días que no hago elucubraciones en Python, así que hoy voy a enseñaros algo que hice ayer por pura necesidad. Se trata de tomar un texto que contiene columnas separadas por tabuladores y transformarlo cambiando de orden unas columnas y suprimiendo otras.
Tengo una hoja Excel donde llevo mis cuentas, y que relleno copiando directamente los asientos desde la página web de mi banco. Las columnas del Excel coinciden con el orden de la web, y por eso basta la copia directa. Pero ayer hacía dos meses que no hacía cuentas (efecto colateral de EQ17KT), y tuve que recurrir al histórico del banco. Y en el histórico, qué mala suerte, las columnas son distintas.
En las dos imágenes siguientes se ve más claro:


Para trabajar con más comodidad, tomé los datos copiados y los guardé en el fichero VISA.CSV. Nada impide insertarlos directamente como texto multilinea en Python, usando la sintaxis de triple comillas:
texto = """ Este es un texto multilínea copiado directamente """
Abrir y leer un fichero en Python es sencillo:
f = open("visa.csv", "r") texto = f.read() f.close()
Otra forma es usando un bloque with, que hace lo mismo pero evitando tener que cerrar el fichero de forma explícita:
with open("visa.csv", "r") as f: texto = f.read()
El texto contiene líneas de texto y otras en blanco:
09/08/2014 1234 1234 1234 5678 CAPRABO - 17 RAMBLA MARIN 44,81 EUR 12/08/2014 1234 1234 1234 5678 BSM ZONA BLAVA 1,54 EUR 13/08/2014 1234 1234 1234 5678 CAPRABO - 17 RAMBLA MARIN 34,34 EUR 16/08/2014 1234 1234 1234 5678 CAPRABO - 17 RAMBLA MARIN 31,02 EUR 03/08/2014 1234 1234 1234 5678 PARKEON 7,40 EUR 18/08/2014 1234 1234 1234 5678 CAPRABO - 17 RAMBLA MARIN 28,82 EUR
Lo que queremos hacer con estos datos es:
- Trocear el texto en líneas.
- Eliminar las líneas en blanco.
- Trocear los campos de cada línea.
- Seleccionar los campos deseados.
- Volver a crear la línea.
- Volver a crear el texto entero.
Vamos allá paso a paso.
Trocear un texto en líneas
Esto es muy fácil en Python con la función split(). A esta función le pasamos un parámetro que es la cadena que queremos usar como separador, y nos devuelve una lista con todos los trozos encontrados.
lineas = texto.split("n") print(lineas) ## ['línea 1', 'línea 2', ...]
Eliminar líneas en blanco
La forma convencional sería un bucle que creara una nueva lísta que contuviera sólo las líneas con contenido. En Python quedaría así:
lineas_datos = [] ## Inicializar lista vacía for linea in lineas: if linea: lineas_datos.append(linea)
Pero Python tiene un mecanismo compacto para generar listas, que convierte el código anterior en una sola línea:
lineas_datos = [linea.strip() for linea in lineas if linea]
Esta sintaxis contiene el bucle (for linea in lineas), la expresión condicional (if linea) y la expresión a aplicar a cada elemento antes de incorporarlo a la lista (linea.strip()). La función strip() la añado porque algunas líneas vienen con espacios en blanco al principio y/o al final y así las limpio de paso.
Trocear los campos de cada línea.
Puesto que tenemos una lista con cada una de las líneas, tendríamos que hacer un bucle línea por línea aplicando a cada una la función split(“t”), que nos trocearía usando el tabulador como división.
Podría volver a usar la forma larga del apartado anterior, pero ya puedes imaginar que preferiré la compacta de nuevo:
lineas_campos = [linea.strip('t') for linea in lineas_datos]
Fíjate que la expresión linea.strip(‘t’) va a generar una lista de campos, y el generador compacto de listas una lista con un elemento por fila. Así, tendremos una estructura de lista de listas:
## [[campo1.1, campo1.2, ...], [campo2.1, campo2.2, ...], ...]
Seleccionar los campos deseados.
Usaremos otro generador compacto para recorrer la lista anterior. El elemento del bucle será en este caso la lista interior, la de los campos. Lo que haremos será crear una lista nueva con los elementos deseados y en el orden correcto:
lista_campos = [[lista[2], lista[0], lista[3][:-5]] for lista in lineas_campos]
El tercer elemento lista[3][:-5] no es más que una cadena de texto de la que quito los 5 últimos caracteres. La sintaxis para trocear un texto en Python es texto_cortado = texto[origen:final], donde origen y final son índices numéricos, pero estos parámetros pueden ser negativos o no ponerse y Python es capaz de imaginarse qué quiso uno decir en cada caso.
Llegados aquí ya tenemos una lista de listas, conteniendo éstas últimas los campos deseados y en el orden correcto. Nos queda revertir proceso, convirtiendo las listas en cadenas con separadores (primero con tabuladores y después con retornos de carro).
Volver a crear la línea.
Para esto tenemos la función inversa de split(), join(). La sintaxis es un poco particular:
print("***".join(["hola", "mundo", "cruel"]) ## hola***mundo***cruel
La cadena a intercalar es a la que se aplica el método, y la lista de cadenas a concatenar aparece como parámetro. Aplicaremos el método a cada sublista de lista_campos, con lo que la lista volverá a tener cadenas (las nuevas líneas).
lista_lineas = ["t".join(campos) for campos in lista_campos]
Volver a crear el texto entero.
Esto es fácil de adivinar: volveremos a aplicar join() a la lista de líneas:
texto_nuevo = "n".join(lista_lineas)
Verás que como aquí sólo hay una lista que se convierte en texto, no se usa el constructor de listas.
Todo de una tacada
En plan lucimiento, se pueden integrar todos los constructores de listas en una sola secuencia. Aquí está el código, por si alguien quiere comprobar que funciona. Los comentarios ilustran la secuencia que se sigue a la hora de ejecutar el código.
with open("visa.csv", "r") as f: ## 0. Aquí se abre el fichero de texto print( "n".join( ## 12. Aquí se unen todas las líneas de la lista en el texto completo y se imprime ['t'.join(L4) ## 11. Aquí volvemos a unir los campos de L4 con tabuladores (saldrá lista de filas) for L4 ## 10. L4 es cada una de esas nuevas listas de campos en 9. in [[L3[2], L3[0], L3[3][:-5]] ## 9. Esto es una nueva lista de campos, con selecc.reordenada de la original L3 ## El tercer elemento es la descripción ## La fecha es el primero ## El cuarto es el importe, quitando los últimos 5 caracteres (" EUR") for L3 ## 8. L3 es cada lista de campos obtenida de la línea L2 in [L2.split("t") ## 7. Trocear cada línea L2 por tabs for L2 ## 6. L2 es cada línea destripada de blancos (excluidas las vacías) in [L.strip() ## 5. Quitar blancos por delante (y detrás) for L ## 3. Para cada línea in f.read(). ## 1. Leer el texto desde el fichero split("n") ## 2. Trocear la cadena en líneas if len(L) > 0] ## 4. Quitar lineas vacías ] ] ] ))