熊猫的多重累计计数

时间:2019-04-30 04:41:20

标签: python pandas pandas-groupby

我有一个DF:

data = [["John","144","Smith","200"], ["Mia","220","John","144"],["Caleb","155","Smith","200"],["Smith","200","Jason","500"]]
data_frame = pd.DataFrame(data,columns = ["Name","ID","Manager_name","Manager_ID"])
data_frame

OP:

    Name    ID    Manager_name  Manager_ID
  0 John    144    Smith             200
  1 Mia     220    John              144
  2 Caleb   155    Smith             200
  3 Smith   200    Jason             500

我正在尝试计算“名称”列中每个人下报告的人数。

逻辑是:

计算该链中单个报告的人数和下属报告的人数。例如史密斯;约翰和卡勒布向史密斯报告,所以2 + 1,而米娅向约翰报告(已经向史密斯报告),所以总数为3。

与杰森类似-> 1,因为史密斯向他报告,而3个人已经向史密斯报告,所以总数为4。

我了解如何通过一些递归以Python的方式进行操作,有没有一种方法可以在Pandas中有效地进行操作。有什么建议吗?

预期的操作次数:

Name        Number of people reporting
John               1
Mia                0
Caleb              0
Smith              3
Jason              4

2 个答案:

答案 0 :(得分:6)

  

斯科特·波士顿的Networkx解决方案是首选解决方案...

有两个解决方案。第一个是矢量化的Pandas类型的解决方案,应该在较大的数据集上快速运行,第二个是pythonic且在OP所寻找的数据集大小上效果不佳,原始df大小为(223635,4)。

  
      
  1. 熊猫解决方案
  2.   

此问题旨在找出组织中每个人管理的人数,包括下属的下属。该解决方案将通过添加作为前几列的管理者的连续列,然后计算该数据框中每个员工的出现次数以确定其下的总数来创建一个数据框。

首先,我们设置输入。

import pandas as pd
import numpy as np

data = [
    ["John", "144", "Smith", "200"],
    ["Mia", "220", "John", "144"],
    ["Caleb", "155", "Smith", "200"],
    ["Smith", "200", "Jason", "500"],
]
df = pd.DataFrame(data, columns=["Name", "SID", "Manager_name", "Manager_SID"])

df = df[["SID", "Manager_SID"]]

# shortening the columns for convenience
df.columns = ["1", "2"]

print(df)

     1    2
0  144  200
1  220  144
2  155  200
3  200  500

首先,必须对没有下属的员工进行计数,并将其放入单独的词典中。

df_not_mngr = df.loc[~df['1'].isin(df['2']), '1']
non_mngr_dict = {str(key):0 for key in df_not_mngr.values}
non_mngr_dict

{'220': 0, '155': 0}

接下来,我们将通过添加上一列的管理器列来修改数据框。最右边一列中没有员工时,循环将停止

for i in range(2, 10):
    df = df.merge(
        df[["1", "2"]], how="left", left_on=str(i), right_on="1", suffixes=("_l", "_r")
    ).drop("1_r", axis=1)
    df.columns = [str(x) for x in range(1, i + 2)]
    if df.iloc[:, -1].isnull().all():
        break
    else:
        continue
print(df)

    1    2    3    4    5
0  144  200  500  NaN  NaN
1  220  144  200  500  NaN
2  155  200  500  NaN  NaN
3  200  500  NaN  NaN  NaN

除第一列外的所有列均被折叠,并对每个员工进行计数并将其添加到字典中。

from collections import Counter

result = dict(Counter(df.iloc[:, 1:].values.flatten()))

将非管理员词典添加到结果中。

result.update(non_mngr_dict)
result

{'200': 3, '500': 4, nan: 8, '144': 1, '220': 0, '155': 0}
  
      
  1. 递归热解
  2.   

我认为这可能比您想要的更加Python化。首先,我创建了一个列表“ all_sids”,以确保我们捕获了所有员工,因为每个列表中都不是全部。

import pandas as pd
import numpy as np

data = [
    ["John", "144", "Smith", "200"],
    ["Mia", "220", "John", "144"],
    ["Caleb", "155", "Smith", "200"],
    ["Smith", "200", "Jason", "500"],
]
df = pd.DataFrame(data, columns=["Name", "SID", "Manager_name", "Manager_SID"])

all_sids = pd.unique(df[['SID', 'Manager_SID']].values.ravel('K'))

然后创建数据透视表。

dfp = df.pivot_table(values='Name', index='SID', columns='Manager_SID', aggfunc='count')

dfp

Manager_SID  144  200  500
SID                       
144          NaN  1.0  NaN
155          NaN  1.0  NaN
200          NaN  NaN  1.0
220          1.0  NaN  NaN

然后是将通过数据透视表汇总所有报告的函数。

def count_mngrs(SID, count=0):
    if str(SID) not in dfp.columns:
        return count
    else:
        count += dfp[str(SID)].sum()
        sid_list = dfp[dfp[str(SID)].notnull()].index
        for sid in sid_list:
            count = count_mngrs(sid, count)
        return count

为每个员工调用该函数并打印结果。

print('SID', '     Number of People Reporting')
for sid in all_sids: 
    print(sid, "     " , int(count_mngrs(sid)))

结果显示在下面,对不起,我在将名称与sid放在一起时有点懒。

SID      Number of People Reporting
144       1
220       0
155       0
200       3
500       4

期待看到更多的熊猫型解决方案!

答案 1 :(得分:4)

这也是一个图形问题,您可以使用Networkx

import networkx as nx
import pandas as pd
data = [["John","144","Smith","200"], ["Mia","220","John","144"],["Caleb","155","Smith","200"],["Smith","200","Jason","500"]]
data_frame = pd.DataFrame(data,columns = ["Name","ID","Manager_name","Manager_ID"])

#create a directed graph object using nx.DiGraph
G = nx.from_pandas_edgelist(data_frame, 
                            source='Name', 
                            target='Manager_name', 
                            create_using=nx.DiGraph())

#use nx.ancestors to get set of "ancenstor" nodes for each node in the directed graph
pd.DataFrame.from_dict({i:len(nx.ancestors(G,i)) for i in G.nodes()}, 
                       orient='index', 
                       columns=['Num of People reporting'])

输出:

       Num of People reporting
John                         1
Smith                        3
Mia                          0
Caleb                        0
Jason                        4

绘制newtorkx:

enter image description here