Search

Python Groupby parte 1/3

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.

Puedes descargarte el código de mi 

Deja una respuesta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.