python pandas-转换df以获取共享一个或多个属性的名称列表

python pandas-转换df以获取共享一个或多个属性的名称列表,python,python-3.x,pandas,Python,Python 3.x,Pandas,从数据帧(df)开始,如下所示: Name AttributeList A 1;2 B 2;3;1 C 4;7 D 8;7;3 我想为每一对可能的名称创建一个新的df,计算它们共享多少属性,并跳过它们不共享任何属性的情况。预期产出如下: Name1 Name2 NumberAttributesShared A B 2 B D 1 C D 1 配对不应该重复,所以如果我有一个B,那么我就不

从数据帧(df)开始,如下所示:

Name AttributeList
A      1;2
B      2;3;1
C      4;7
D      8;7;3
我想为每一对可能的名称创建一个新的df,计算它们共享多少属性,并跳过它们不共享任何属性的情况。预期产出如下:

Name1 Name2 NumberAttributesShared
A       B      2
B       D      1
C       D      1
配对不应该重复,所以如果我有一个B,那么我就不应该有B。 在本例中,A和C不共享任何属性,因此A和C对未列出。另一方面,对AB的值为2,因为它们共享两个属性


有什么聪明有效的方法可以达到这个目标吗?

假设您没有重复的ID,简单的解决方案如下

z = list(zip(names, map(set, atts.str.split(';').tolist())))

dict_ = dict()

for i in range(len(z)):
    for j in range(i+1, len(z)):
        inter = (z[i][1].intersection(z[j][1]))
        if inter:
            dict_[(z[i][0], z[j][0])] = len(z[i][1].intersection(z[j][1]))

pd.DataFrame(dict_, index=['NumberAttributesShared']).T.reset_index()
当然,在纯python中,不利用任何库作为
itertools
。你明白了,可以尝试一些改进

level_0 level_1 NumberAttributesShared
0   A   B       2
1   B   D       1
2   C   D       1

由于我们正在处理
str
set
的循环和集合,因此您可能不想为此使用
pandas
。使用纯python并在
pd.DataFrame
中输入您的输出,最后

假设您没有重复的ID,简单的解决方案如下

z = list(zip(names, map(set, atts.str.split(';').tolist())))

dict_ = dict()

for i in range(len(z)):
    for j in range(i+1, len(z)):
        inter = (z[i][1].intersection(z[j][1]))
        if inter:
            dict_[(z[i][0], z[j][0])] = len(z[i][1].intersection(z[j][1]))

pd.DataFrame(dict_, index=['NumberAttributesShared']).T.reset_index()
当然,在纯python中,不利用任何库作为
itertools
。你明白了,可以尝试一些改进

level_0 level_1 NumberAttributesShared
0   A   B       2
1   B   D       1
2   C   D       1

由于我们正在处理
str
set
的循环和集合,因此您可能不想为此使用
pandas
。在纯python中工作,并在
pd.DataFrame中输入您的输出。最后

从生成附加列开始,即
属性列表的副本
, 但作为属性列表(而不是字符串或int):

然后,为了加快单个元素的读取速度,将
Name
复制到索引:

df.set_index('Name', drop=False, inplace=True)
然后可以计算每个2元素的公共属性数 名称组合:

lst = []
for names in itertools.combinations(df1.Name, 2):
    n1, n2 = names
    s1 = set(df.at[n1, 'AttrList'])
    s2 = set(df.at[n2, 'AttrList'])
    cnt = len(s1.intersection(s2))
    if cnt > 0:
        lst.append([n1, n2, cnt])
最后,您可以生成结果:

result = pd.DataFrame(lst, columns=['Name1', 'Name2', 'NumberAttributesShared'])
当然,您应该从导入itertools开始

编辑 示例数据只包含带“;”分隔的字符串。 现在,正如您所指出的,属性列表可以包含一个单个数字, 我意识到单个字段的类型可以是
string
int

要正确读取这两种情况下的属性,请在
df['AttrList']=…
说明将右侧更改为:

df.AttributeList.astype(str).str.split(';')
(添加了
.astype(str)
要转换为正确的类型,我更改了此详细信息 同上)

如何解决性能问题 如何加速计算的提示(但不是完整的解决方案)

对辅助表进行热处理:

dfSgl = df[df.AttributeList.astype(str).str.isdigit()]
仅包含具有单个属性的行

我的示例数据包含具有单个属性的四行:

['E', 2], ['F', 3], ['G', 4], ['H', 4]
因此,在我的例子中,
dfSgl
包含:

     Name AttributeList
Name                   
E       E             2
F       F             3
G       G             4
H       H             4
然后执行:

dfSgl.groupby('AttributeList').filter(lambda x: len(x) > 1)
在这种情况下:

     Name AttributeList
Name                   
G       G             4
H       H             4
这意味着
G
H
都有一个公共属性(4)

这可能不是这些对象的最终结果, 因为它们的(单个)属性可以出现在其他属性的属性列表中 具有多个属性的对象

然后,您必须将上述“单件”与其他对象进行比较 使用多个
属性,可能会添加一些常用属性 他们的帐户

剩下的部分是仅比较具有多个属性的对象, 正如我在开始时解释的,加入结果。所以至少 问题的规模将更小

自2019年2月9日起编辑 我的第一个解决方案完全基于熊猫,但结果证明它是可行的 比较慢

所以我想出了另一个更快的解决方案,基于Numpy和Pandas

这个想法是:

  • 将Name列设置为df的索引:

  • 将Attr列添加到df,其中包含数字列表(属性):

  • 需要计算每个对象的属性向量

  • 我们需要一个辅助功能:

    def genAttrList(lst, len):
        res = np.zeros(len, dtype='B')
        for n in lst:
            res[n] = 1
        return res
    
    def ordered(df):
        res = df.copy()
        res[['Name1', 'Name2']] = np.sort(res[['Name1', 'Name2']].values, axis=1)
        return res.sort_values(['NumberAttributesShared', 'Name1', 'Name2'],
            ascending=[False, True, True]).reset_index(drop=True)
    
  • 生成一个属性向量,属性向量的位置对应于 来自lst的属性(编号)。第二个参数(len)指定 此向量的长度-最大属性+1(不使用元素0)

    dtype='B'(无符号字节),基于以下假设: 属性的数目小于256。相比之下,它降低了内存需求 默认设置(在本例中)int类型

  • 计算解决方案的函数是:

    def fun3(df):
        ind = df.index
        attrLen = df.Attr.map(lambda x: x[-1]).max() + 1
        attr = np.array(df.Attr.transform(lambda x:
            genAttrList(x, attrLen)).tolist())
        counts = np.count_nonzero(np.bitwise_and(
            attr[np.newaxis, :], attr[:, np.newaxis]), axis=(2))
        return pd.DataFrame(data=[ (ind[x[0]], ind[x[1]], counts[x])
            for x in zip(*np.nonzero(np.triu(counts, 1)))],
            columns=['Name1', 'Name2', 'NumberAttributesShared'])
    
  • 第一部分是计算attr数组(2-D)。每行代表数据 对于特定用户-编码为0和1序列的属性列表

    然后按如下方式计算阵列计数:

    • 按位_和是为两个attr实例计算的,带有额外的 轴在不同的地方。实际上,和是为每个 可能的一对对象
    • 然后(根据上一步的结果)计算 count_非零,计算每个属性共有多少个属性 一对物体
    此解决方案的威力来自:

    • 事实上,按位_和是一个通用函数,它的运行速度非常快 比“普通”Python函数更快
    • Numpy广播,这是一种非常有效的技术,几乎不需要花费太多时间 内存需求
    • 使用添加的轴,允许对每对轴执行计算 列表中的对象
    最后一步是计算(并返回)实际结果(DataFrame)。 值得注意的是:

    • triu返回主数组中元素为零的计数数组 对角线及以下
    • 非零返回上述数组中非零元素的索引
    • 这样我们就可以从上三角得到非零元素的指数 计数(主对角线上方)。 这种限制
      result = fun3(df)
      
      def ordered(df):
          res = df.copy()
          res[['Name1', 'Name2']] = np.sort(res[['Name1', 'Name2']].values, axis=1)
          return res.sort_values(['NumberAttributesShared', 'Name1', 'Name2'],
              ascending=[False, True, True]).reset_index(drop=True)
      
      ordResult = ordered(result)
      
      ordResult.equals(ordAnotherResult)