I'm a bit new in python and I'm trying to achieve what I can't with VBA, get things faster.
I have large datasets like this in the source(just a sample):
This is done also by python and it's a CSV file.
What my code has to do with this is:
- Get the column "Planned" And "Department Planned".
- Convert the schedules in "Planned" column to 30 min intervals between starting and ending. Some schedules can be weird like 09:37-15:42 (so they need to be handled, normalizing the intervals from 9:30 to 15:30)
- With that list, calculate the minutes worked in another column refering to the original schedule, so in the example 09:37 will have an interval starting from 9:30 but there will be only 23 mins working there.
- explode the columns from points 2 and 3 to rows repeating the column Department Planned and Location NAme.
- Group by: Location Name, Date Time (from point 3), Department Planned. Adding the minutes worked.
This is the code:
#calcula_Presentes_Contratados
import pandas as pd
from itertools import groupby
from datetime import datetime, timedelta
def configuraDataFrame(df, horario, modo):
#drop unwanted columns and filter empty rows
df = df[df.columns.intersection([
'CentroPlanificacion',
'Fecha',
horario,
modo
])]
df = df[df[horario].notna()]
if df.empty:
return df
return df[df[horario].str.contains(":")]
def fechaYHora(horario, fecha, posicion):
#calculate time and date for each start and ending time if the ending time < starting time, add one day to the ending.
hora = datetime.strptime(horario.split('-')[posicion], '%H:%M')
fecha = datetime.strptime(fecha, '%Y-%m-%d')
if posicion == 1:
horaI = datetime.strptime(horario.split('-')[0], '%H:%M')
horaI = datetime(fecha.year, fecha.month, fecha.day, horaI.hour, 0)
if hora.minute >= 30:
hora = datetime(fecha.year, fecha.month, fecha.day, hora.hour, 30)
else:
hora = datetime(fecha.year, fecha.month, fecha.day, hora.hour, 0)
if posicion == 1 and hora < horaI: hora += timedelta(days=1)
return hora
def extraeRangos(horario, fecha, aplicaSeparador):
try:
#get all match schedules if there are any
partido = horario.split('/')
listahorarios = []
#loop through al the schedules and add the range with the split separator "Separa aquí"
for horarios in partido:
rangoactual = pd.date_range(start=fechaYHora(horarios, fecha, 0), end=fechaYHora(horarios, fecha, 1), freq="30min").strftime('%Y-%m-%d, %H:%M').to_list()
listahorarios += rangoactual
if aplicaSeparador: listahorarios.append('Separa aquí')
return listahorarios
except:
return
def calculaMinutosInicio(horaInicio, tramoInicial):
#function to calculate starting minutes
horaInicio = datetime.strptime(horaInicio,'%H:%M')
tramoFin = datetime.strptime(tramoInicial.split(", ")[1], '%H:%M') + timedelta(minutes=30)
if tramoFin < horaInicio: horaFin += timedelta(days=1)
return tramoFin - horaInicio
def calculaMinutosFin(horaFin, tramoFin):
#function to calculate ending minutes
horaFin = datetime.strptime(horaFin,'%H:%M')
if horaFin.minute == 0 or horaFin.minute == 30: return timedelta(seconds=0)
tramoFin = datetime.strptime(tramoFin.split(", ")[1], '%H:%M')
return horaFin - tramoFin
def generaListasPorPartido(lista_tramos):
lista_final = []
listaTemp = []
#loop through the list looking for "Separa aquí"
for tramo in lista_tramos:
#if found, all the items stored before in the list will go in a single list
if tramo == 'Separa aquí':
lista_final.append(listaTemp)
listaTemp = []
#store all items in between "Separa aquí"
else:
listaTemp.append(tramo)
#return a list of lists for each matching schedule
return lista_final
def generaListaTramos(lista_tramos, horario):
listatramos = []
#calculate starting minutes from starting time to starting interval
minutosInicio = int(calculaMinutosInicio(horario.split("-")[0], lista_tramos[0]).seconds / 60)
#calculate ending minutes from ending time to ending interval
minutosFin = int(calculaMinutosFin(horario.split("-")[1], lista_tramos[-1]).seconds / 60)
for tramo in lista_tramos:
#the first index will have the starting minutes
if lista_tramos.index(tramo) == 0:
listatramos.append(minutosInicio)
#the last index will have the ending minutes
elif tramo == lista_tramos[-1]:
listatramos.append(minutosFin)
#every interval in between will always have 30 minutes
else:
listatramos.append(30)
return listatramos
def calculaConexion(lista_tramos, horario):
#if the list is not empty
if len(lista_tramos) > 0:
listatramos = []
#gnerate a list for each match schedule
lista_tramos = generaListasPorPartido(lista_tramos)
horario: horario = horario.split("/")
#add the working minutes to each interval
for i in range(len(lista_tramos)):
listatramos.extend(generaListaTramos(lista_tramos[i], horario[i]))
return listatramos
def eliminaSeparadores(lista):
return list(filter(('Separa aquí').__ne__, lista))
def generaRangoHoras(df, campo, nuevo_campo):
#generate range of times column
df[nuevo_campo] = df.apply(lambda row: extraeRangos(row[campo], row['Fecha'], True), axis=1)
#generate working minutes column
df['Conexión_' + nuevo_campo] = df.apply(lambda row: calculaConexion(row[nuevo_campo], row[campo]), axis=1)
#get rid of the manual separator for match schedules
df[nuevo_campo] = df.apply(lambda row: eliminaSeparadores(row[nuevo_campo]), axis=1)
return df
def explotaHoras(df, listaSinExplotar):
df = df.set_index(listaSinExplotar).apply(pd.Series.explode).fillna('').reset_index()
return df
def main(rutaficheros):
#read soruce file
rutaExcel = rutaficheros + 'BaseACC.csv'
dfOrigen = pd.read_csv(rutaExcel, converters={i: str for i in range(100)})
#create lists to loop
listaCamposHorario = ['Programado', 'Programado 2']
listaCamposModo = ['Modo Planificado', 'Modo Planificado 2']
#for each item in list, create the time range, working minutes and export the file
for horario, modo in zip(listaCamposHorario, listaCamposModo):
df = configuraDataFrame(dfOrigen, horario, modo)
if not df.empty:
df = generaRangoHoras(df, horario, 'Franjas_Programado')
df = explotaHoras(df, ['CentroPlanificacion', 'Fecha', horario, modo])
#drop unwanted columns for the final result
df = df.drop([horario, 'Fecha'], axis=1)
#drop unwanted rows for the final result
df = df[df['Conexión_Franjas_Programado'] > 0]
#group all the working minutes by location, planned/planned2 and department/department2
df = df.groupby(['CentroPlanificacion', modo, 'Franjas_Programado']).agg({'Conexión_Franjas_Programado' : 'sum'}).reset_index()
df.to_csv(rutaficheros + horario + '.csv', index=False)
return
And this should be the output:
The output file:
This takes around 35 seconds when I have a 100k rows source file and I think it could be better but I'm still very new in python to know more efficient ways to code.
Sample data
Dirección,Territorio,DNI,Agente,CentroPlanificacion,Campaña Origen,Fecha,Presentes,Contratados,ModoPlanificacion,Habilidades Agente,Programado,Modo Planificado,Programado 2,Modo Planificado 2
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-01,LIBRE,LIBRE,Department1,Department1,LIBRE,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-02,09:00-15:00,09:00-15:00,Department1,Department1,09:00-13:00,Department1,13:00-15:00,Department2
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-03,09:00-15:00,09:00-15:00,Department1,Department1,09:00-10:00/11:00-14:00/15:00-18:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-04,09:00-15:00,09:00-15:00,Department1,Department1,22:00-06:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-05,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-06,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-07,LIBRE,LIBRE,Department1,Department1,LIBRE,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-08,LIBRE,LIBRE,Department1,Department1,LIBRE,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-09,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-10,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-11,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-12,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-13,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-14,LIBRE,LIBRE,Department1,Department1,LIBRE,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-15,LIBRE,LIBRE,Department1,Department1,LIBRE,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-16,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-17,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-18,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-19,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-20,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-21,LIBRE,LIBRE,Department1,Department1,LIBRE,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-22,LIBRE,LIBRE,Department1,Department1,LIBRE,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-23,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-24,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-25,VAC,09:00-15:00,Department1,Department1,VAC,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-26,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-27,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-28,LIBRE,LIBRE,Department1,Department1,LIBRE,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-29,LIBRE,LIBRE,Department1,Department1,LIBRE,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-30,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
BussinessName,TerritoryNAme,WorkerID1,WorkerName1,Location1,Department1,2022-05-31,09:00-15:00,09:00-15:00,Department1,Department1,09:00-15:00,Department1,,
calculaMinutosInicio
work? It refers to ahoraFin
that is not defined in scope. \$\endgroup\$horaFin
is wrong, shoudl betramoFin
\$\endgroup\$