Hoy comienzo una nueva aventura: mi Blog!, y un reto: escribir regularmente y no morir en el intento. Que la Fuerza me acompañe.
Dentro de unos pocos días harán 5 años desde que comencé a desarrollar una aplicación con Ignition, de Inductive Automation.
La plataforma está genial como sistema SCADA. Permite conectarse a un gran número de fuentes de datos, y desarrollar proyectos en Jython, lenguaje basado en Python e implementado en Java.
Pero tiene un pequeño problema. No se pueden instalar módulos escritos en C. Así que la potencia en el tratamiento de datos se ve sensíblemente reducida, al no poder instalar módulos como Pandas o Numpy.
En este, mi primer post, voy a poner un ejemplo de cómo hacer una operación trivial, y muy común, con un conjunto de datos: Agrupar y agregar (sumar en este caso). Y lo voy a hacer de dos maneras: primero utilizando Pandas y luego, con Python puro.
No podemos comenzar el ejercicico sin lo más importante, los datos. He preparado una tabla muy sencilla en un archivo csv que podéis descargar aquí. Contiene unos datos de producción de las plantas de una empresa.
La tabla contiene las siguientes columnas:
- ZONA: Zona geográfica donde está situada la planta correspondiente.
- PLANTA: Nombre de la planta.
- DIA_MES: Día del mes del que se han obtenido los datos.
- HORAS_TRABAJO: Las horas de los trabajos que se han ido realizando en la planta.
Bien. Vamos ha intentar obtener la suma de los trabajos en la planta por día del mes.
Utilizando Pandas realizaríamos los siguientes pasos:
1.- Leer el archivo csv y colocarlo en un dataframe.
import pandas as pd df = pd.read_csv('trabajos_01.csv', sep=";", decimal=",")
2.- Echamos un vistazo a la tabla:
print (df.head()) print (df.tail()) ZONA PLANTA DIA_MES HORAS_TRABAJO 0 NORTE GORBEA 1 0.983333 1 NORTE GORBEA 1 0.900000 2 NORTE GORBEA 1 0.533333 3 NORTE GORBEA 1 0.950000 4 NORTE GORBEA 1 1.083333 ZONA PLANTA DIA_MES HORAS_TRABAJO 550 NORTE GORBEA 29 0.500000 551 NORTE GORBEA 29 2.000000 552 NORTE GORBEA 29 0.250000 553 NORTE GORBEA 29 0.966667 554 NORTE GORBEA 29 1.333333
3.- Creamos el objeto GroupBy, utilizando la columna ‘DIA_MES’
grouped = df.groupby(['DIA_MES'])
print (type(grouped)) ...<class 'pandas.core.groupby.DataFrameGroupBy'>
4.- Agregamos los datos, obteniendo la suma de las horas por día.
res = grouped['HORAS_TRABAJO'].sum()
Lo pasamos a un dataframe.
res_df = pd.DataFrame({'DIA_MES':res.index, 'HORAS_TRABAJO':res.values})
Echamos un vistazo al resultado, confirmando que hemos agrupado las horas de trabajo por día del mes.
print(res_df.head()) print(res_df.tail()) DIA_MES HORAS_TRABAJO 0 1 21.783333 1 2 5.950000 2 4 14.283333 3 5 14.366667 4 6 23.000000 DIA_MES HORAS_TRABAJO 23 25 14.983333 24 26 15.150000 25 27 13.833333 26 28 19.583333 27 29 14.333333
Recopilo todo el código utilizado:
import pandas as pd df = pd.read_csv('trabajos_01.csv', sep=";", decimal=",") grouped = df.groupby(['DIA_MES']) res = grouped['HORAS_TRABAJO'].sum() res_df = pd.DataFrame({'DIA_MES':res.index, 'HORAS_TRABAJO':res.values})
import pandas as pd df = pd.read_csv('trabajos_01.csv', sep=";", decimal=",") grouped = df.groupby(['DIA_MES']) res = grouped['HORAS_TRABAJO'].sum() res_df = pd.DataFrame({'DIA_MES':res.index, 'HORAS_TRABAJO':res.values})
Ahora vamos a conseguir el mismo resultado pero sin utilizar Pandas.
1.- Leer el archivo csv y colocarlo en una lista. Para ello utilizaremos el módulo csv incluido en Python.
import csv csv_rdr = csv.reader(open('trabajos_01.csv'), delimiter=';') trabajos_list=[] for index, row in enumerate(csv_rdr): if index == 0: encabezado = row else: row[3] = float(row[3].replace(",", ".")) trabajos_list.append(row)
Nótese que la primera lista contenida en csv_rdr sería el encabezado de la tabla
Lo que nos deja una lista con la siguiente pinta:
[['NORTE', 'GORBEA', '01', 0.9833333333333333], ['NORTE', 'GORBEA', '01', 0.9], ...]
Ahora, para agrupar las horas de trabajo por el día de la semana sugiero dos métodos
1.- Hemos importado del módulo operator la funcion itemgetter. Ésta función se aplica a una serie, y devuelve el valor de la serie que corresponde a la posición o a la clave definida en la función. En nuestro caso queremos que obtenga el valor de la posisión 2 de nuestra lista, que corresponde al DIA_MES, para poder hacer la agrupación. Pero por si sola no nos va a hacer el trabajo, necesitamos la ayuda de la clase groupby del módulo itertools incluido en Python.
La clase groupby admite dos argumentos. Primero el iterable, nuestra lista en nuestro cosa. Importante ordenarla bajo el mismo criterio de posición o clave. En este caso la ordenamos por DIA_MES, que es la posición 2. El segundo argumento que admite la clase groupby es la clave con la seleccionará el elemento a devolver. Con esto se creará un iterador con los elementos correspondientes al mismo valor de DIA_MES.
El código completo sería el siguiente:
from operator import itemgetter from itertools import groupby res = [] trabajos_group = groupby(sorted(trabajos_list, key=itemgetter(2)), itemgetter(2)) for key, grp in trabajos_group: res.append([key, sum(ele[3] for ele in grp)])
Podemos ver la pinta que tiene una key y lo que contiene el grp correspondiente:
for key, grp in trabajos_group: print (key) print (grp) for item in grp: print (item)
Por ejemplo, para el DIA_MES = 01 tendríamos:
01 <itertools._grouper object at 0x000001CDEBEBDA90> ['NORTE', 'GORBEA', '01', 0.9833333333333333] ['NORTE', 'GORBEA', '01', 0.9] ['NORTE', 'GORBEA', '01', 0.5333333333333333] ['NORTE', 'GORBEA', '01', 0.95] ['NORTE', 'GORBEA', '01', 1.0833333333333333] ['NORTE', 'GORBEA', '01', 0.95] ['NORTE', 'GORBEA', '01', 0.9333333333333333] ['NORTE', 'GORBEA', '01', 1.1666666666666667] ['NORTE', 'GORBEA', '01', 0.9666666666666667] ['NORTE', 'GORBEA', '01', 0.95] ['NORTE', 'GORBEA', '01', 1.25] ['NORTE', 'GORBEA', '01', 0.85] ['NORTE', 'GORBEA', '01', 0.48333333333333334] ['NORTE', 'GORBEA', '01', 0.8666666666666667] ['NORTE', 'GORBEA', '01', 0.36666666666666664] ['NORTE', 'GORBEA', '01', 1.9666666666666666] ['NORTE', 'GORBEA', '01', 0.8833333333333333] ['NORTE', 'GORBEA', '01', 0.9166666666666666] ['NORTE', 'GORBEA', '01', 1.6] ['NORTE', 'GORBEA', '01', 0.016666666666666666] ['NORTE', 'GORBEA', '01', 0.016666666666666666] ['NORTE', 'GORBEA', '01', 0.016666666666666666] ['NORTE', 'GORBEA', '01', 1.05] ['NORTE', 'GORBEA', '01', 0.9333333333333333] ['NORTE', 'GORBEA', '01', 1.15]
En la linea de código
res.append([key, sum(ele[3] for ele in grp)])
Lo que se hace es añadir el valor de DIA_MES y la suma de los valores correspondientes a HORAS_TRABAJO que se encuentran en la posición 3 de los items de cada iterador grp.
Lo que nos deja el siguiente resultado
01 21.78333333333333 02 5.949999999999999 04 14.283333333333335 05 14.366666666666667 06 22.99999999999999 ##################### 25 14.983333333333333 26 15.15 27 13.833333333333334 28 19.58333333333334 29 14.333333333333334
2.- Utilizar la clase defaultdict del módulo collections incluido en Python.
from collections import defaultdict res_2 = [] d = defaultdict(list) for row in trabajos_list: d[row[2]].append(float(row[3])) for k, v in sorted(d.items()): res_2.append([k, sum(v)])
Con el resultado siguiente
01 21.78333333333333 02 5.949999999999999 04 14.283333333333335 05 14.366666666666667 06 22.99999999999999 ##################### 25 14.983333333333333 26 15.15 27 13.833333333333334 28 19.58333333333334 29 14.333333333333334
Como se puede comprobar, ambos scripts producen el mismo resultado, que a su vez es igual al obtenido utilizando Pandas.