重新分配唯一值 - pandas DataFrame

11 python numpy dataframe assign pandas

我试图assign unique重视pandas df特定的个人.

对于df下方,[Area]并且[Place]将一起组成unique是不同的值工作.这些值将分配给个人,总体目标是尽可能少地使用个人.

诀窍是这些值不断开始和结束,并持续不同的时间长度.unique任何一次分配给个人的最多值是3.[On]显示[Place]和[Area]正在发生的当前唯一值的数量.

因此,这为我需要的人数提供了具体的指导.例如3个unique值一个= 1个人,6个独特值= 2个人

我不能做一个groupby声明我在那里assign的第一3 unique valuesindividual 1再下一个3个uniqueindividual 2等.

我设想的是,当unique值大于3时,我想[Area]先将值组合,然后合并剩余的.因此,assign[Area]个人(最多3个)中查找相同的值.然后,如果有_leftover_(<3),则应尽可能将它们组合成3个组.

我设想这种工作的方式是:通过一个展望未来hour.对于每个新row值,script应该看到有多少个值[On](这表明需要多少个人).当unique值> 3,他们应该是assigned通过grouping在相同的值[Area].如果有剩余的价值,无论如何它们应该组合成一组3.

对于df下面的数目unique出现的值用于[Place][Area]1-6之间变化.所以我们永远不应该超过2个人assigned.当unique值> 3时,应首先指定[Area].在剩余的值应该与具有小于3倍其他个人相结合unique的值.

为大df道歉.这是我能够复制问题的唯一方法!

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()]) < 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))
Run Code Online (Sandbox Code Playgroud)

输出:

        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
Run Code Online (Sandbox Code Playgroud)

预期输出和评论我认为应该分配的原因:

在此输入图像描述

tel*_*tel 5

这是在线答案的现场版本,您可以自己尝试.

问题

你看到的错误是由于你的问题的另一个有趣的边缘情况.在6th工作期间,代码分配person 2(A, House 4).然后它看到该区域A在一小时内重复,因此它person 2在该区域中保持不变.这使得person 2下一个工作无法进入,这是在区域内B.

但是,由于已经分配了区域和地点的唯一组合,因此没有理由为了发生作业而保留person 2区域.A(A, House 1)(A, House 1)person 1

解决方案

在决定何时在一个区域内举行人时,只考虑区域和地点的唯一组合可以解决该问题.只需要改变几行代码.

首先,我们构建一个与唯一(区域,地点)对对应的区域列表:

unqareas = df[['Area', 'Place']].drop_duplicates()['Area'].values
Run Code Online (Sandbox Code Playgroud)

然后,我们刚刚替补unqareas用于areas在识别保持代码的第一行:

ixrep = np.argmax(np.triu(unqareas.reshape(-1, 1)==unqareas, k=1), axis=1)
Run Code Online (Sandbox Code Playgroud)

完整列表/测试

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))
Run Code Online (Sandbox Code Playgroud)

输出:

      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
Run Code Online (Sandbox Code Playgroud)