usar tutorial tablas recorrer para notebook leer hacer funciones filtrar español datos data como python pandas numpy dataframe assign

python - tutorial - recorrer data frame pandas



Reasignar valores de columna en un pandas df (3)

Esta pregunta está relacionada con las listas o el personal. Estoy tratando de asignar varios trabajos a individuos (empleados). Usando el df continuación,

`[Person]` = Individuals (employees) `[Area]` and `[Place]` = unique jobs `[On]` = How many unique jobs are occurring at each point in time

Así que [Area] y [Place] juntos conformarán valores unique que son trabajos diferentes. Estos valores se asignarán a individuos con el objetivo general de utilizar la menor cantidad posible de individuos. El valor más exclusivo assigned a cualquier individuo es 3. [On] muestra cuántos valores unique actuales para [Place] y [Area] están ocurriendo. Así que esto proporciona una guía concreta sobre cuántas personas necesito. Por ejemplo,

1-3 unique values occurring = 1 individual 4-6 unique values occurring = 2 individuals 7-9 unique values occurring = 3 individuals etc

Pregunta: Donde la cantidad de valores unique en [Area] y [Place] es mayor que 3 me está causando problemas. No puedo hacer un grupo groupby cual assign los primeros 3 unique values al individual 1 y los siguientes 3 valores unique al individual 2 etc. Quiero agrupar valores únicos en [Area] y [Place] por [Area] . Así que busque assign mismos valores en [Area] a un individuo (hasta 3). Luego, si hay valores remanentes (<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 cuántos individuos se requieren en total). 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.

Poner eso en un proceso paso a paso:

1) Use la Column [On] para determinar cuántas personas se requieren al mirar hacia el futuro durante una hour

2) Cuando se producen más de 3 valores unique , asigne primero los valores idénticos en [Area] .

3) Si hay valores remanentes , busque combinar de todos modos.

Para el df continuación, hay 9 valores unique que ocurren para [Place] y [Area] con una hour . Entonces deberíamos tener 3 individuos assigned . Cuando los valores unique > 3 deben ser asignados por [Area] y ver si ocurre el mismo valor. Los valores remanentes deben combinarse con otros individuos que tengan menos de 3 valores unique .

import pandas as pd import numpy as np d = ({ ''Time'' : [''8:03:00'',''8:17:00'',''8:20:00'',''8:28:00'',''8:35:00'',''08:40:00'',''08:42:00'',''08:45:00'',''08:50:00''], ''Place'' : [''House 1'',''House 2'',''House 3'',''House 4'',''House 5'',''House 1'',''House 2'',''House 3'',''House 2''], ''Area'' : [''A'',''B'',''C'',''D'',''E'',''D'',''E'',''F'',''G''], ''On'' : [''1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'',''9''], ''Person'' : [''Person 1'',''Person 2'',''Person 3'',''Person 4'',''Person 5'',''Person 4'',''Person 5'',''Person 6'',''Person 7''], }) df = pd.DataFrame(data=d)

Este es mi intento:

def reduce_df(df): values = df[''Area''] + df[''Place''] df1 = df.loc[~values.duplicated(),:] # ignore duplicate values for this part.. person_count = df1.groupby(''Person'')[''Person''].agg(''count'') leftover_count = person_count[person_count < 3] # the ''leftovers'' # try merging pairs together nleft = leftover_count.shape[0] to_try = np.arange(nleft - 1) to_merge = (leftover_count.values[to_try] + leftover_count.values[to_try + 1]) <= 3 to_merge[1:] = to_merge[1:] & ~to_merge[:-1] to_merge = to_try[to_merge] merge_dict = dict(zip(leftover_count.index.values[to_merge+1], leftover_count.index.values[to_merge])) def change_person(p): if p in merge_dict.keys(): return merge_dict[p] return p reduced_df = df.copy() # update df with the merges you found reduced_df[''Person''] = reduced_df[''Person''].apply(change_person) return reduced_df df1 = (reduce_df(reduce_df(df)))

Esta es la salida:

Time Place Area On Person 0 8:03:00 House 1 A 1 Person 1 1 8:17:00 House 2 B 2 Person 1 2 8:20:00 House 3 C 3 Person 1 3 8:28:00 House 4 D 4 Person 4 4 8:35:00 House 5 E 5 Person 5 5 8:40:00 House 1 D 6 Person 4 6 8:42:00 House 2 E 7 Person 5 7 8:45:00 House 3 F 8 Person 5 8 8:50:00 House 2 G 9 Person 7

Esta es mi salida prevista:

Time Place Area On Person 0 8:03:00 House 1 A 1 Person 1 1 8:17:00 House 2 B 2 Person 1 2 8:20:00 House 3 C 3 Person 1 3 8:28:00 House 4 D 4 Person 2 4 8:35:00 House 5 E 5 Person 3 5 8:40:00 House 6 D 6 Person 2 6 8:42:00 House 2 E 7 Person 3 7 8:45:00 House 3 F 8 Person 2 8 8:50:00 House 2 G 9 Person 3

Descripción de cómo quiero obtener esta salida:

Index 0: One `unique` value occurring. So `assign` to individual 1 Index 1: Two `unique` values occurring. So `assign` to individual 1 Index 2: Three `unique` values occurring. So `assign` to individual 1 Index 3: Four `unique` values on. So `assign` to individual 2 Index 4: Five `unique` values on. This one is a bit tricky and hard to conceptualise. But there is another `E` within an `hour`. So `assign` to a new individual so it can be combined with the other `E` Index 5: Six `unique` values on. Should be `assigned` with the other `D`. So individual 2 Index 6: Seven `unique` values on. Should be `assigned` with other `E`. So individual 3 Index 7: Eight `unique` values on. New value in `[Area]`, which is a _leftover_. `Assign` to either individual 2 or 3 Index 8: Nine `unique` values on. New value in `[Area]`, which is a _leftover_. `Assign` to either individual 2 or 3

Ejemplo No2:

d = ({ ''Time'' : [''8:03:00'',''8:17:00'',''8:20:00'',''8:28:00'',''8:35:00'',''8:40:00'',''8:42:00'',''8:45:00'',''8:50:00''], ''Place'' : [''House 1'',''House 2'',''House 3'',''House 1'',''House 2'',''House 3'',''House 1'',''House 2'',''House 3''], ''Area'' : [''X'',''X'',''X'',''X'',''X'',''X'',''X'',''X'',''X''], ''On'' : [''1'',''2'',''3'',''3'',''3'',''3'',''3'',''3'',''3''], ''Person'' : [''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1''], }) df = pd.DataFrame(data=d)

Estoy recibiendo un error:

IndexError: index 1 is out of bounds for axis 1 with size 1

En esta línea:

df.loc[:,''Person''] = df[''Person''].unique()[assignedPeople]

Sin embargo, si cambio la persona a 1,2,3 repitiendo, devuelve lo siguiente:

''Person'' : [''Person 1'',''Person 2'',''Person 3'',''Person 1'',''Person 2'',''Person 3'',''Person 1'',''Person 2'',''Person 3''], Time Place Area On Person 0 8:03:00 House 1 X 1 Person 1 1 8:17:00 House 2 X 2 Person 1 2 8:20:00 House 3 X 3 Person 1 3 8:28:00 House 1 X 3 Person 2 4 8:35:00 House 2 X 3 Person 2 5 8:40:00 House 3 X 3 Person 2 6 8:42:00 House 1 X 3 Person 3 7 8:45:00 House 2 X 3 Person 3 8 8:50:00 House 3 X 3 Person 3

Salida prevista:

Time Place Area On Person 0 8:03:00 House 1 X 1 Person 1 1 8:17:00 House 2 X 2 Person 1 2 8:20:00 House 3 X 3 Person 1 3 8:28:00 House 1 X 3 Person 1 4 8:35:00 House 2 X 3 Person 1 5 8:40:00 House 3 X 3 Person 1 6 8:42:00 House 1 X 3 Person 1 7 8:45:00 House 2 X 3 Person 1 8 8:50:00 House 3 X 3 Person 1

La principal conclusión del ejemplo 2 es:

1) There are <3 unique values on so assign to individual 1


Actualizar

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

Aquí hay una respuesta en la forma de la función allocatePeople . Se basa en precomputar todos los índices en los que las áreas se repiten en una hora:

from collections import Counter import numpy as np import pandas as pd 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()]) < 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

Tenga en cuenta el uso de df[''Person''].unique() en allocatePeople . Que maneja el caso donde las personas se repiten en la entrada. Se supone que el orden de las personas en la entrada es el orden deseado en el que esas personas deberían ser asignadas.

Probé allocatePeople contra la entrada de ejemplo del OP ( example1 y example2 ) y también contra un par de casos de borde que se me ocurrió que creo que (?) Coinciden con el algoritmo deseado del OP:

ds = dict( example1 = ({ ''Time'' : [''8:03:00'',''8:17:00'',''8:20:00'',''8:28:00'',''8:35:00'',''08:40:00'',''08:42:00'',''08:45:00'',''08:50:00''], ''Place'' : [''House 1'',''House 2'',''House 3'',''House 4'',''House 5'',''House 1'',''House 2'',''House 3'',''House 2''], ''Area'' : [''A'',''B'',''C'',''D'',''E'',''D'',''E'',''F'',''G''], ''On'' : [''1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'',''9''], ''Person'' : [''Person 1'',''Person 2'',''Person 3'',''Person 4'',''Person 5'',''Person 4'',''Person 5'',''Person 6'',''Person 7''], }), example2 = ({ ''Time'' : [''8:03:00'',''8:17:00'',''8:20:00'',''8:28:00'',''8:35:00'',''8:40:00'',''8:42:00'',''8:45:00'',''8:50:00''], ''Place'' : [''House 1'',''House 2'',''House 3'',''House 1'',''House 2'',''House 3'',''House 1'',''House 2'',''House 3''], ''Area'' : [''X'',''X'',''X'',''X'',''X'',''X'',''X'',''X'',''X''], ''On'' : [''1'',''2'',''3'',''3'',''3'',''3'',''3'',''3'',''3''], ''Person'' : [''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1'',''Person 1''], }), long_repeats = ({ ''Time'' : [''8:03:00'',''8:17:00'',''8:20:00'',''8:25:00'',''8:30:00'',''8:31:00'',''8:35:00'',''8:45:00'',''8:50:00''], ''Place'' : [''House 1'',''House 2'',''House 3'',''House 4'',''House 1'',''House 1'',''House 2'',''House 3'',''House 2''], ''Area'' : [''A'',''A'',''A'',''A'',''B'',''C'',''C'',''C'',''B''], ''Person'' : [''Person 1'',''Person 1'',''Person 1'',''Person 2'',''Person 3'',''Person 4'',''Person 4'',''Person 4'',''Person 3''], ''On'' : [''1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'',''9''], }), many_repeats = ({ ''Time'' : [''8:03:00'',''8:17:00'',''8:20:00'',''8:28:00'',''8:35:00'',''08:40:00'',''08:42:00'',''08:45:00'',''08:50:00''], ''Place'' : [''House 1'',''House 2'',''House 3'',''House 4'',''House 1'',''House 1'',''House 2'',''House 1'',''House 2''], ''Area'' : [''A'', ''B'', ''C'', ''D'', ''D'', ''E'', ''E'', ''F'', ''F''], ''On'' : [''1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'',''9''], ''Person'' : [''Person 1'',''Person 1'',''Person 1'',''Person 2'',''Person 3'',''Person 4'',''Person 3'',''Person 5'',''Person 6''], }), large_gap = ({ ''Time'' : [''8:03:00'',''8:17:00'',''8:20:00'',''8:28:00'',''8:35:00'',''08:40:00'',''08:42:00'',''08:45:00'',''08:50:00''], ''Place'' : [''House 1'',''House 2'',''House 3'',''House 4'',''House 1'',''House 1'',''House 2'',''House 1'',''House 3''], ''Area'' : [''A'', ''B'', ''C'', ''D'', ''E'', ''F'', ''D'', ''D'', ''D''], ''On'' : [''1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'',''9''], ''Person'' : [''Person 1'',''Person 1'',''Person 1'',''Person 2'',''Person 3'',''Person 4'',''Person 3'',''Person 5'',''Person 6''], }), different_times = ({ ''Time'' : [''8:03:00'',''8:17:00'',''8:20:00'',''8:28:00'',''8:35:00'',''08:40:00'',''09:42:00'',''09:45:00'',''09:50:00''], ''Place'' : [''House 1'',''House 2'',''House 3'',''House 4'',''House 1'',''House 1'',''House 2'',''House 1'',''House 1''], ''Area'' : [''A'', ''B'', ''C'', ''D'', ''D'', ''E'', ''E'', ''F'', ''G''], ''On'' : [''1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'',''9''], ''Person'' : [''Person 1'',''Person 1'',''Person 1'',''Person 2'',''Person 3'',''Person 4'',''Person 3'',''Person 5'',''Person 6''], }) ) expectedPeoples = dict( example1 = [1,1,1,2,3,2,3,2,3], example2 = [1,1,1,1,1,1,1,1,1], long_repeats = [1,1,1,2,2,3,3,3,2], many_repeats = [1,1,1,2,2,3,3,2,3], large_gap = [1,1,1,2,3,3,2,2,3], different_times = [1,1,1,2,2,2,3,3,3], ) for name,d in ds.items(): df = pd.DataFrame(d) expected = [''Person %d'' % i for i in expectedPeoples[name]] ap = allocatePeople(df) print(name, ap, sep=''/n'', end=''/n/n'') np.testing.assert_array_equal(ap[''Person''], expected)

Las declaraciones assert_array_equal pasan y la salida coincide con la salida esperada de OP:

example1 Time Place Area On Person 0 8:03:00 House 1 A 1 Person 1 1 8:17:00 House 2 B 2 Person 1 2 8:20:00 House 3 C 3 Person 1 3 8:28:00 House 4 D 4 Person 2 4 8:35:00 House 5 E 5 Person 3 5 08:40:00 House 1 D 6 Person 2 6 08:42:00 House 2 E 7 Person 3 7 08:45:00 House 3 F 8 Person 2 8 08:50:00 House 2 G 9 Person 3 example2 Time Place Area On Person 0 8:03:00 House 1 X 1 Person 1 1 8:17:00 House 2 X 2 Person 1 2 8:20:00 House 3 X 3 Person 1 3 8:28:00 House 1 X 3 Person 1 4 8:35:00 House 2 X 3 Person 1 5 8:40:00 House 3 X 3 Person 1 6 8:42:00 House 1 X 3 Person 1 7 8:45:00 House 2 X 3 Person 1 8 8:50:00 House 3 X 3 Person 1

La salida para mis casos de prueba coincide con mis expectativas también:

long_repeats Time Place Area Person On 0 8:03:00 House 1 A Person 1 1 1 8:17:00 House 2 A Person 1 2 2 8:20:00 House 3 A Person 1 3 3 8:25:00 House 4 A Person 2 4 4 8:30:00 House 1 B Person 2 5 5 8:31:00 House 1 C Person 3 6 6 8:35:00 House 2 C Person 3 7 7 8:45:00 House 3 C Person 3 8 8 8:50:00 House 2 B Person 2 9 many_repeats Time Place Area On Person 0 8:03:00 House 1 A 1 Person 1 1 8:17:00 House 2 B 2 Person 1 2 8:20:00 House 3 C 3 Person 1 3 8:28:00 House 4 D 4 Person 2 4 8:35:00 House 1 D 5 Person 2 5 08:40:00 House 1 E 6 Person 3 6 08:42:00 House 2 E 7 Person 3 7 08:45:00 House 1 F 8 Person 2 8 08:50:00 House 2 F 9 Person 3 large_gap Time Place Area On Person 0 8:03:00 House 1 A 1 Person 1 1 8:17:00 House 2 B 2 Person 1 2 8:20:00 House 3 C 3 Person 1 3 8:28:00 House 4 D 4 Person 2 4 8:35:00 House 1 E 5 Person 3 5 08:40:00 House 1 F 6 Person 3 6 08:42:00 House 2 D 7 Person 2 7 08:45:00 House 1 D 8 Person 2 8 08:50:00 House 3 D 9 Person 3 different_times Time Place Area On Person 0 8:03:00 House 1 A 1 Person 1 1 8:17:00 House 2 B 2 Person 1 2 8:20:00 House 3 C 3 Person 1 3 8:28:00 House 4 D 4 Person 2 4 8:35:00 House 1 D 5 Person 2 5 08:40:00 House 1 E 6 Person 2 6 09:42:00 House 2 E 7 Person 3 7 09:45:00 House 1 F 8 Person 3 8 09:50:00 House 1 G 9 Person 3

Déjame saber si hace todo lo que quieres, o si todavía necesita algunos ajustes. Creo que todos están ansiosos por verte cumplir tu visión.


Al escribir mi otra respuesta , poco a poco me di cuenta de que el algoritmo del OP podría ser más fácil de implementar con un enfoque que se centre en los trabajos (que pueden ser diferentes), en lugar de las personas (que son todas iguales). Aquí hay una solución que utiliza el enfoque centrado en el trabajo:

from collections import Counter import numpy as np import pandas as pd def assignJob(job, assignedix, areasPerPerson): for i in range(len(assignedix)): if (areasPerPerson - len(assignedix[i])) >= len(job): assignedix[i].extend(job) return True else: return False def allocatePeople(df, areasPerPerson=3): areas = df[''Area''].values times = pd.to_datetime(df[''Time'']).values peopleUniq = df[''Person''].unique() npeople = int(np.ceil(areas.size / float(areasPerPerson))) # 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()]) < np.timedelta64(1, ''h'') jobs =[] _jobdict = {} for i,(area,hold) in enumerate(zip(areas, holds)): if hold: _jobdict[area] = job = _jobdict.get(area, []) + [i] if len(job)==areasPerPerson: jobs.append(_jobdict.pop(area)) elif area in _jobdict: jobs.append(_jobdict.pop(area) + [i]) else: jobs.append([i]) jobs.sort() assignedix = [[] for i in range(npeople)] for job in jobs: if not assignJob(job, assignedix, areasPerPerson): # break the job up and try again for subjob in ([sj] for sj in job): assignJob(subjob, assignedix, areasPerPerson) df = df.copy() for i,aix in enumerate(assignedix): df.loc[aix, ''Person''] = peopleUniq[i] return df

Esta versión de allocatePeople también se ha probado ampliamente y pasa todas las mismas comprobaciones descritas en mi otra respuesta.

Tiene más bucles que mi otra solución, por lo que es probable que sea un poco menos eficiente (aunque solo importará si su marco de datos es muy grande, digamos 1e6 filas y más). Por otro lado, es algo más corto y, creo, más sencillo y fácil de entender.


Ok, antes de profundizar en la lógica del problema, vale la pena hacer un poco de limpieza para ordenar los datos y llevarlos a un formato más útil:

#Create table of unique people unique_people = df[[''Person'']].drop_duplicates().sort_values([''Person'']).reset_index(drop=True) #Reformat time column df[''Time''] = pd.to_datetime(df[''Time''])

Ahora, llegando a la lógica del problema, es útil dividir el problema en etapas. En primer lugar, desearemos crear trabajos individuales (con números de trabajo) basados ​​en el "Área" y el tiempo entre ellos. Es decir, trabajos en la misma área, dentro de una hora pueden compartir el mismo número de trabajo.

#Assign jobs df= df.sort_values([''Area'',''Time'']).reset_index(drop=True) df[''Job no''] = 0 current_job = 1 df.loc[0,''Job no''] = current_job for i in range(rows-1): prev_row = df.loc[i] row = df.loc[i+1] time_diff = (row[''Time''] - prev_row[''Time'']).seconds //3600 if (row[''Area''] == prev_row[''Area'']) & (time_diff == 0): pass else: current_job +=1 df.loc[i+1,''Job no''] = current_job

Con este paso ahora fuera del camino, es una simple cuestión de asignar "Personas" a trabajos individuales:

df= df.sort_values([''Job no'']).reset_index(drop=True) df[''Person''] = "" df_groups = df.groupby(''Job no'') for group in df_groups: group_size = group[1].count()[''Time''] for person_idx in range(len(unique_people)): person = unique_people.loc[person_idx][''Person''] person_count = df[df[''Person'']==person][''Person''].count() if group_size <= (3-person_count): idx = group[1].index.values df.loc[idx,''Person''] = person break

Y finalmente,

df= df.sort_values([''Time'']).reset_index(drop=True) print(df)

He intentado codificar esto de una manera que es más fácil de deseleccionar, por lo que puede haber eficiencias aquí. Sin embargo, el objetivo era establecer la lógica utilizada.

Este código proporciona los resultados esperados en ambos conjuntos de datos, por lo que espero que responda a su pregunta.