Reasignar valores únicos – pandas DataFrame

Estoy tratando de assign valores unique en pandas df a individuos específicos.

Para el df continuación, [Area] y [Place] formarán juntos valores unique que son varios trabajos . Estos valores se asignarán a individuos con el objective general de utilizar la menor cantidad posible de individuos.

El truco es que estos valores se inician y terminan constantemente y se extienden por diferentes períodos de tiempo. Los valores más unique asignados a un individuo en cualquier momento son 3 . [On] muestra cuántos valores únicos actuales para [Lugar] y [Área] están ocurriendo.

Así que esto proporciona una guía concreta sobre cuántas personas necesito. por ejemplo, 3 valores unique una = 1 persona, 6 valores únicos en = 2 personas

No puedo hacer una statement groupby en la que assign los primeros 3 unique values al individual 1 y los siguientes 3 valores unique al individual 2 etc.

Lo que imagino es que, cuando los valores unique sean mayores que 3, primero quiero agrupar los valores en [Area] y luego combinar las sobras. Así que busque assign mismos valores en [Area] a un individuo (hasta 3). Luego, si hay valores de _leftover_ (<3) , deben combinarse para formar un grupo de 3, cuando sea posible.

La forma en que me imagino este trabajo es: mira el futuro por una hour . Para cada nueva row de valores, el script debe ver cuántos valores estarán [On] (esto proporciona una indicación de la cantidad total de individuos que se requieren). Cuando los valores unique son> 3, deben assigned grouping el mismo valor en [Area] . Si hay valores remanentes , deben combinarse de todos modos para formar un grupo de 3.

Para el df continuación, el número de valores unique que aparecen para [Place] y [Area] varía entre 1-6. Así que nunca debemos tener más de 2 individuos assigned . Cuando los valores unique son> 3, primero debe ser asignado por [Area] . Los valores remanentes deben combinarse con otros individuos que tengan menos de 3 valores unique .

Disculpas por la gran df. ¡Es la única manera en que puedo replicar el problema!

 import pandas as pd import numpy as np from collections import Counter d = ({ 'Time' : ['8:03:00','8:17:00','8:20:00','8:33:00','8:47:00','8:48:00','9:03:00','9:15:00','9:18:00','9:33:00','9:45:00','9:48:00','10:03:00','10:15:00','10:15:00','10:15:00','10:18:00','10:32:00','10:33:00','10:39:00','10:43:00','10:48:00','10:50:00','11:03:00','11:03:00','11:07:00','11:25:00','11:27:00','11:42:00','11:48:00','11:51:00','11:57:00','12:00:00','12:08:00','12:15:00','12:17:00','12:25:00','12:30:00','12:35:00','12:39:00','12:47:00','12:52:00','12:55:00','13:00:00','13:03:00','13:07:00','13:12:00','13:15:00','13:22:00','13:27:00','13:27:00'], 'Area' : ['A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','B','A','B','A','A','A','A','B','A','A','B','B','A','B','C','A','B','C','C','A','B','C','C','B','A','C','B','C','C','A','C','B','C','C','A','C'], 'Place' : ['House 1','House 2','House 3','House 1','House 3','House 2','House 1','House 3','House 2','House 1','House 3','House 2','House 1','House 3','House 4','House 1','House 2','House 1','House 1','House 4','House 3','House 2','House 1','House 1','House 4','House 1','House 1','House 4','House 1','House 1','House 4','House 1','House 2','House 1','House 4','House 1','House 1','House 2','House 1','House 4','House 1','House 1','House 3','House 2','House 4','House 1','House 2','House 4','House 1','House 4','House 2'], 'On' : ['1','2','3','3','3','3','3','3','3','3','3','3','3','3','4','5','5','5','5','5','5','4','3','3','3','2','2','2','2','3','3','3','4','4','4','4','4','4','4','4','4','4','4','4','4','4','5','6','6','6','6'], 'Person' : ['Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 2','Person 3','Person 1','Person 3','Person 1','Person 2','Person 1','Person 1','Person 3','Person 1','Person 2','Person 3','Person 3','Person 2','Person 3','Person 4','Person 2','Person 3','Person 4','Person 4','Person 2','Person 3','Person 4','Person 4','Person 3','Person 2','Person 4','Person 3','Person 4','Person 4','Person 2','Person 4','Person 3','Person 5','Person 4','Person 2','Person 4'], }) df = pd.DataFrame(data=d) def getAssignedPeople(df, areasPerPerson): areas = df['Area'].values places = df['Place'].values times = pd.to_datetime(df['Time']).values maxPerson = np.ceil(areas.size / float(areasPerPerson)) - 1 assignmentCount = Counter() assignedPeople = [] assignedPlaces = {} heldPeople = {} heldAreas = {} holdAvailable = True person = 0 # search for repeated areas. Mark them if the next repeat occurs within an hour ixrep = np.argmax(np.triu(areas.reshape(-1, 1)==areas, k=1), axis=1) holds = np.zeros(areas.size, dtype=bool) holds[ixrep.nonzero()] = (times[ixrep[ixrep.nonzero()]] - times[ixrep.nonzero()]) = areasPerPerson: # the current person is already assigned to enough areas, move on to the next a = heldPeople.pop(person, None) heldAreas.pop(a, None) person += 1 if area in heldAreas: # assign to the person held in this area p = heldAreas.pop(area) heldPeople.pop(p) else: # get the first non-held person. If we need to hold in this area, # also make sure the person has at least 2 free assignment slots, # though if it's the last person assign to them anyway p = person while p in heldPeople or (hold and holdAvailable and (areasPerPerson - assignmentCount[p] < 2)) and not p==maxPerson: p += 1 assignmentCount.update([p]) assignedPlaces[(area, place)] = p assignedPeople.append(p) if hold: if p==maxPerson: # mark that there are no more people available to perform holds holdAvailable = False # this area recurrs in an hour, mark that the person should be held here heldPeople[p] = area heldAreas[area] = p return assignedPeople def allocatePeople(df, areasPerPerson=3): assignedPeople = getAssignedPeople(df, areasPerPerson=areasPerPerson) df = df.copy() df.loc[:,'Person'] = df['Person'].unique()[assignedPeople] return df print(allocatePeople(df)) 

Salida:

  Time Area Place On Person 0 8:03:00 A House 1 1 Person 1 1 8:17:00 A House 2 2 Person 1 2 8:20:00 A House 3 3 Person 1 3 8:33:00 A House 1 3 Person 1 4 8:47:00 A House 3 3 Person 1 5 8:48:00 A House 2 3 Person 1 6 9:03:00 A House 1 3 Person 1 7 9:15:00 A House 3 3 Person 1 8 9:18:00 A House 2 3 Person 1 9 9:33:00 A House 1 3 Person 1 10 9:45:00 A House 3 3 Person 1 11 9:48:00 A House 2 3 Person 1 12 10:03:00 A House 1 3 Person 1 13 10:15:00 A House 3 3 Person 1 14 10:15:00 A House 4 4 Person 2 15 10:15:00 B House 1 5 Person 2 16 10:18:00 A House 2 5 Person 1 17 10:32:00 B House 1 5 Person 2 18 10:33:00 A House 1 5 Person 1 19 10:39:00 A House 4 5 Person 2 20 10:43:00 A House 3 5 Person 1 21 10:48:00 A House 2 4 Person 1 22 10:50:00 B House 1 3 Person 2 23 11:03:00 A House 1 3 Person 1 24 11:03:00 A House 4 3 Person 2 25 11:07:00 B House 1 2 Person 2 26 11:25:00 B House 1 2 Person 2 27 11:27:00 A House 4 2 Person 2 28 11:42:00 B House 1 2 Person 2 29 11:48:00 C House 1 3 Person 2 30 11:51:00 A House 4 3 Person 2 31 11:57:00 B House 1 3 Person 2 32 12:00:00 C House 2 4 Person 3 33 12:08:00 C House 1 4 Person 2 34 12:15:00 A House 4 4 Person 2 35 12:17:00 B House 1 4 Person 2 36 12:25:00 C House 1 4 Person 2 37 12:30:00 C House 2 4 Person 3 38 12:35:00 B House 1 4 Person 2 39 12:39:00 A House 4 4 Person 2 40 12:47:00 C House 1 4 Person 2 41 12:52:00 B House 1 4 Person 2 42 12:55:00 C House 3 4 Person 3 43 13:00:00 C House 2 4 Person 3 44 13:03:00 A House 4 4 Person 2 45 13:07:00 C House 1 4 Person 2 46 13:12:00 B House 2 5 Person 3 47 13:15:00 C House 4 6 Person 4 48 13:22:00 C House 1 6 Person 2 49 13:27:00 A House 4 6 Person 2 50 13:27:00 C House 2 6 Person 3 

Resultado previsto y comentarios sobre por qué creo que debería asignarse:

introduzca la descripción de la imagen aquí

Hay una versión en vivo de esta respuesta en línea que puedes probar por ti mismo.

El problema

El error que está viendo se debe a (otro) caso interesante de su problema. Durante el 6th trabajo, el código asigna a la person 2 a (A, House 4) . Luego ve que el área A repite dentro de una hora, por lo que mantiene a la person 2 en esa área. Esto hace que la person 2 no esté disponible para el siguiente trabajo, que está en el área B

Sin embargo, no hay razón para mantener a la person 2 en el área A por el bien de un trabajo que ocurre en (A, House 1) , ya que la combinación única de área y lugar (A, House 1) ya ha sido asignada a la person 1 .

La solución

El problema se puede solucionar considerando solo combinaciones únicas de área y lugar cuando se decide cuándo mantener a una persona en un área. Sólo un par de líneas de código tienen que cambiar.

Primero, construimos una lista de áreas que corresponden a los pares únicos (área, lugar):

 unqareas = df[['Area', 'Place']].drop_duplicates()['Area'].values 

Luego simplemente sustituimos las areas unqareas en la primera línea del código que identifica las retenciones:

 ixrep = np.argmax(np.triu(unqareas.reshape(-1, 1)==unqareas, k=1), axis=1) 

Listado completo / prueba

 import pandas as pd import numpy as np from collections import Counter d = ({ 'Time' : ['8:03:00','8:07:00','8:10:00','8:23:00','8:27:00','8:30:00','8:37:00','8:40:00','8:48:00'], 'Place' : ['House 1','House 2','House 3','House 1','House 2','House 3','House 4','House 1','House 1'], 'Area' : ['A','A','A','A','A','A','A','B','A'], 'Person' : ['Person 1','Person 1','Person 1','Person 1','Person 1','Person 1','Person 2','Person 3','Person 1'], 'On' : ['1','2','3','3','3','3','4','5','5'] }) df = pd.DataFrame(data=d) def getAssignedPeople(df, areasPerPerson): areas = df['Area'].values unqareas = df[['Area', 'Place']].drop_duplicates()['Area'].values places = df['Place'].values times = pd.to_datetime(df['Time']).values maxPerson = np.ceil(areas.size / float(areasPerPerson)) - 1 assignmentCount = Counter() assignedPeople = [] assignedPlaces = {} heldPeople = {} heldAreas = {} holdAvailable = True person = 0 # search for repeated areas. Mark them if the next repeat occurs within an hour ixrep = np.argmax(np.triu(unqareas.reshape(-1, 1)==unqareas, k=1), axis=1) holds = np.zeros(areas.size, dtype=bool) holds[ixrep.nonzero()] = (times[ixrep[ixrep.nonzero()]] - times[ixrep.nonzero()]) < np.timedelta64(1, 'h') for area,place,hold in zip(areas, places, holds): if (area, place) in assignedPlaces: # this unique (area, place) has already been assigned to someone assignedPeople.append(assignedPlaces[(area, place)]) continue if assignmentCount[person] >= areasPerPerson: # the current person is already assigned to enough areas, move on to the next a = heldPeople.pop(person, None) heldAreas.pop(a, None) person += 1 if area in heldAreas: # assign to the person held in this area p = heldAreas.pop(area) heldPeople.pop(p) else: # get the first non-held person. If we need to hold in this area, # also make sure the person has at least 2 free assignment slots, # though if it's the last person assign to them anyway p = person while p in heldPeople or (hold and holdAvailable and (areasPerPerson - assignmentCount[p] < 2)) and not p==maxPerson: p += 1 assignmentCount.update([p]) assignedPlaces[(area, place)] = p assignedPeople.append(p) if hold: if p==maxPerson: # mark that there are no more people available to perform holds holdAvailable = False # this area recurrs in an hour, mark that the person should be held here heldPeople[p] = area heldAreas[area] = p return assignedPeople def allocatePeople(df, areasPerPerson=3): assignedPeople = getAssignedPeople(df, areasPerPerson=areasPerPerson) df = df.copy() df.loc[:,'Person'] = df['Person'].unique()[assignedPeople] return df print(allocatePeople(df)) 

Salida:

  Time Place Area Person On 0 8:03:00 House 1 A Person 1 1 1 8:07:00 House 2 A Person 1 2 2 8:10:00 House 3 A Person 1 3 3 8:23:00 House 1 A Person 1 3 4 8:27:00 House 2 A Person 1 3 5 8:30:00 House 3 A Person 1 3 6 8:37:00 House 4 A Person 2 4 7 8:40:00 House 1 B Person 2 5 8 8:48:00 House 1 A Person 1 5