Reordenar columnas de texto con una sola sentencia Python

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:

datos recientes
Formato de los datos recientes
Formato de los datos históricos
Formato de los datos históricos

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:

  1. Trocear el texto en líneas.
  2. Eliminar las líneas en blanco.
  3. Trocear los campos de cada línea.
  4. Seleccionar los campos deseados.
  5. Volver a crear la línea.
  6. 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
                        ]
                    ]
              ]
    ))
Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s