# Обновить

Вот ответ в виде функции `allocatePeople` . Он основан на предварительном вычислении всех индексов, где области повторяются в течение часа:

``````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``````

Обратите внимание на использование `df['Person'].unique()` в `allocatePeople` . Это обрабатывает случай, когда люди повторяются на входе. Предполагается, что порядок людей на входе является желаемым порядком, в котором эти люди должны быть назначены.

Я протестировал `example2` входных данных примера OP ( `example1` и `example2` ), а также на паре крайних случаев, с которыми я столкнулся, и думаю (?) Соответствуют желаемому алгоритму 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)``````

Операторы `assert_array_equal` проходят, и выходные данные соответствуют ожидаемым выходным данным 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``````

Вывод для моих тестов также соответствует моим ожиданиям:

``````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``````

Дайте мне знать, если он делает все, что вы хотели, или он все еще нуждается в некоторых настройках. Я думаю, что все хотят видеть, как вы выполняете свое видение.

Этот вопрос связан с составлением списка или кадровым составом. Я пытаюсь назначить различные рабочие места отдельным лицам (сотрудникам). Используя `df` ниже,

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

Таким образом, `[Area]` и `[Place]` вместе образуют `unique` значения, которые являются разными заданиями. Эти значения будут назначены лицам с общей целью использовать как можно меньше людей. Наиболее уникальные значения, `assigned` любому отдельному человеку, равны 3. `[On]` Отображает, сколько текущих `unique` значений для `[Place]` и `[Area]` встречаются. Так что это конкретное руководство о том, сколько людей мне нужно. Например,

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

Вопрос: Если количество `unique` значений в `[Area]` и `[Place]` больше 3, это вызывает у меня проблемы. Я не могу выполнить `groupby` которой я `assign` первые `3 unique values` `individual 1` и следующие 3 `unique` значения `individual 2` и т. Д. Я хочу сгруппировать уникальные значения в `[Area]` и `[Place]` по `[Area]` . Так что смотрите, чтобы `assign` человеку одинаковые значения в `[Area]` (до 3). Затем, если есть оставшиеся значения (<3), их следует объединить, чтобы сделать группу из 3, где это возможно.

Я представляю себе эту работу так: заглядывать в будущее на `hour` . Для каждой новой `row` значений `script` должен видеть, сколько значений будет `[On]` (Это показывает, сколько всего требуется человек). Если `unique` значения> 3, они должны быть `assigned` путем `grouping` одного и того же значения в `[Area]` . Если есть оставшиеся значения, их все равно следует объединить, чтобы получить группу из 3.

Поместив это в пошаговый процесс:

1) Используйте `Column` `[On]` Чтобы определить, сколько человек требуется, заглядывая в будущее на `hour`

2) Если имеется более 3 `unique` значений, присвойте сначала идентичные значения в `[Area]` .

3) Если есть какие-либо оставшиеся значения, поищите, как это возможно.

Для `df` ниже, есть 9 `unique` значений для `[Place]` и `[Area]` с `hour` . Таким образом, у нас должно быть `assigned` 3 человека. Когда `unique` значения> 3, он должен быть назначен `[Area]` и видеть, происходит ли то же самое значение. Оставшиеся значения должны быть объединены с другими лицами, которые имеют менее 3 `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)``````

Это моя попытка:

``````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)))``````

Это выход:

``````       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``````

Это мой предполагаемый результат:

``````       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``````

Описание того, как я хочу получить этот вывод:

``````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``````

Пример №2:

``````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)``````

Я получаю ошибку:

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

На этой линии:

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

Однако, если я изменяю Person на 1,2,3 с повторением, он возвращает следующее:

``````'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``````

Предполагаемый выход:

``````      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``````

Основной вывод из Примера 2:

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

При написании моего другого ответа я постепенно пришел к мысли, что алгоритм OP может быть проще реализовать с помощью подхода, который фокусируется на рабочих местах (которые могут отличаться), а не на людях (которые все одинаковы). Вот решение, которое использует подход, ориентированный на работу:

``````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``````

Эта версия `allocatePeople` также была тщательно протестирована и проходит все те же проверки, которые описаны в моем другом ответе.

В нем больше циклов, чем в моем другом решении, поэтому он, вероятно, будет несколько менее эффективным (хотя это будет иметь значение только в том случае, если ваш фрейм данных очень большой, скажем, `1e6` строк и выше). С другой стороны, он несколько короче и, как мне кажется, более прост и понятен.