在熊猫数据框中对重复的列 ID 进行分组

Posted

技术标签:

【中文标题】在熊猫数据框中对重复的列 ID 进行分组【英文标题】:Group duplicate column IDs in pandas dataframe 【发布时间】:2017-12-13 08:55:45 【问题描述】:

现在有很多类似的问题,但大多数都回答了如何删除重复的列。但是,我想知道如何制作一个元组列表,其中每个元组都包含重复列的列名。我假设每一列都有一个唯一的名称。只是为了进一步说明我的问题:

df = pd.DataFrame('A': [1, 2, 3, 4, 5],'B': [2, 4, 2, 1, 9],
                   'C': [1, 2, 3, 4, 5],'D': [2, 4, 2, 1, 9],
                   'E': [3, 4, 2, 1, 2],'F': [1, 1, 1, 1, 1],
                   index = ['a1', 'a2', 'a3', 'a4', 'a5'])

然后我想要输出:

[('A', 'C'), ('B', 'D')]

如果您今天感觉很好,那么也可以将相同的问题扩展到行。如何获取每个元组包含重复行的元组列表。

【问题讨论】:

我在现实生活中的 df 有 (8000, 500) 的暗淡,所以如果我尝试所有的组合方法,那将是非常耗时的。我有一个解决方案,但这仅对数值有效(但不适用于定性变量)。所以我想的是对每一列进行规范化,并取df的点积和转置的df。然后收集所有点积为“1”且不在矩阵对角线上的列。 我为 numpy 解决方案添加了 numpy 标签 - 非常快,但有点复杂。 当您说您想要一种也适用于非数值的方法时,是否包括object-dtypes?或者你的意思是类似于字符串的USB dtypes? String 列,级别为:“aa”、“ab”、“bc”等。我不想对这些级别使用编码。 @MSeifert 【参考方案1】:

不用panda,只用纯python:

data = 'A': [1, 2, 3, 4, 5],'B': [2, 4, 2, 1, 9],
        'C': [1, 2, 3, 4, 5],'D': [2, 4, 2, 1, 9],
        'E': [3, 4, 2, 1, 2],'F': [1, 1, 1, 1, 1]
from collections import defaultdict

deduplicate = defaultdict(list)


for key, items in data.items():
    deduplicate[tuple(items)].append(key)  # cast to tuple because they are hashables but lists are not.

duplicates = list()
for vector, letters in deduplicate.items():
    if len(letters) > 1:
        duplicates.append(letters)

print(duplicates)

使用熊猫:

import pandas

df = pandas.DataFrame(data)
duplicates = []

dedup2 = defaultdict(list)

for key in df.columns:
    dedup2[tuple(df[key])].append(key)

duplicates = list()
for vector, letters in dedup2.items():
    if len(letters) > 1:
        duplicates.append(letters)

print(duplicates)

不是很好,但可能会更快,因为一切都在数据的一次迭代中完成。

dedup2 = defaultdict(list)

duplicates = 

for key in df.columns:
    astup = tuple(df[key])
    duplic = dedup2[astup] 
    duplic.append(key)
    if len(duplic) > 1:
        duplicates[astup] = duplic

duplicates = duplicates.values()
print(duplicates)

【讨论】:

如果您可以使用数据框作为输入,很乐意将其包含在我的计时测试中。 我不知道熊猫:/你能给我链接,以便它可以很容易地被引导吗? 不确定您所说的引导是什么意思。如果您的意思是加载模块:import pandas as pd。然后,我们需要将df 从问题翻译成本文中使用的data。我自己对熊猫的了解非常有限。 嗨,我已将代码修复为与 pandas 的数据框兼容。然而,它可能是相当不理想的。【参考方案2】:

这是一个单线

In [22]: from itertools import combinations

In [23]: [x for x in combinations(df.columns, 2) if (df[x[0]] == df[x[-1]]).all()]
Out[23]: [('A', 'C'), ('B', 'D')]

或者,使用 NumPy 广播。更好,看看 Divakar 的solution

In [124]: cols = df.columns

In [125]: dftv = df.T.values

In [126]: cross = pd.DataFrame((dftv == dftv[:, None]).all(-1), cols, cols)

In [127]: cross
Out[127]:
       A      B      C      D      E      F
A   True  False   True  False  False  False
B  False   True  False   True  False  False
C   True  False   True  False  False  False
D  False   True  False   True  False  False
E  False  False  False  False   True  False
F  False  False  False  False  False   True

# Only take values from lower triangle
In [128]: s = cross.where(np.tri(*cross.shape, k=-1)).unstack()

In [129]: s[s == 1].index.tolist()
Out[129]: [('A', 'C'), ('B', 'D')]

【讨论】:

不错的单行。但是对于大型数据集,使用组合可能会很昂贵。 不错!我有 500 个特征(500 列)。这将是昂贵的。如果您有任何更快的方法,请也包括在内。 @PallavBakshi 我不知道你的数据集的细节,但如果你想减少维度,为什么不直接采用相关性呢? A 和 C 之间的相关性将为 1。 您为什么不将每个人提供的解决方案计时到您的特定用例并将结果添加到问题中? @ayhan - 我认为这是一种非常标准的删除重复项的方法。但是,我想把它们放在一个列表中而不是丢弃它们。但是谢谢:D【参考方案3】:

基于@John Galt 一个像这样的班轮:

result_col = [x for x in combinations(df.columns, 2) if (df[x[0]] == df[x[-1]]).all()]

您可以通过以下方式获取result_row

result_row = [x for x in combinations(df.T.columns,2) if (df.T[x[0]] == df.T[x[-1]]).all()]

使用transpose (df.T)

【讨论】:

【参考方案4】:

这也应该这样做:

[tuple(d.index) for _,d in df.T.groupby(list(df.T.columns)) if len(d) > 1]

产量:

# [('A', 'C'), ('B', 'D')]

【讨论】:

这似乎是最干净的选择。【参考方案5】:

这是仅使用推导式/内置函数的另一种选择:

filter(lambda x: len(x) > 1, list(set([tuple([x for x in df.columns if all(df[x] == df[y])]) for y in df.columns])))

结果:

[('A', 'C'), ('B', 'D')]

【讨论】:

【参考方案6】:

这是一种 NumPy 方法 -

def group_duplicate_cols(df):
    a = df.values
    sidx = np.lexsort(a)
    b = a[:,sidx]

    m = np.concatenate(([False], (b[:,1:] == b[:,:-1]).all(0), [False] ))
    idx = np.flatnonzero(m[1:] != m[:-1])
    C = df.columns[sidx].tolist()
    return [C[i:j] for i,j in zip(idx[::2],idx[1::2]+1)]

示例运行 -

In [100]: df
Out[100]: 
    A  B  C  D  E  F
a1  1  2  1  2  3  1
a2  2  4  2  4  4  1
a3  3  2  3  2  2  1
a4  4  1  4  1  1  1
a5  5  9  5  9  2  1

In [101]: group_duplicate_cols(df)
Out[101]: [['A', 'C'], ['B', 'D']]

# Let's add one more duplicate into group containing 'A'
In [102]: df.F = df.A

In [103]: group_duplicate_cols(df)
Out[103]: [['A', 'C', 'F'], ['B', 'D']]

转换做同样的事情,但是对于行(索引),我们只需要沿着另一个轴切换操作,就像这样 -

def group_duplicate_rows(df):
    a = df.values
    sidx = np.lexsort(a.T)
    b = a[sidx]

    m = np.concatenate(([False], (b[1:] == b[:-1]).all(1), [False] ))
    idx = np.flatnonzero(m[1:] != m[:-1])
    C = df.index[sidx].tolist()
    return [C[i:j] for i,j in zip(idx[::2],idx[1::2]+1)]

示例运行 -

In [260]: df2
Out[260]: 
   a1  a2  a3  a4  a5
A   3   5   3   4   5
B   1   1   1   1   1
C   3   5   3   4   5
D   2   9   2   1   9
E   2   2   2   1   2
F   1   1   1   1   1

In [261]: group_duplicate_rows(df2)
Out[261]: [['B', 'F'], ['A', 'C']]

基准测试

方法-

# @John Galt's soln-1
from itertools import combinations
def combinations_app(df):
    return[x for x in combinations(df.columns, 2) if (df[x[0]] == df[x[-1]]).all()]

# @Abdou's soln
def pandas_groupby_app(df):
    return [tuple(d.index) for _,d in df.T.groupby(list(df.T.columns)) if len(d) > 1]                        

# @COLDSPEED's soln
def triu_app(df):
    c = df.columns.tolist()
    i, j = np.triu_indices(len(c), 1)
    x = [(c[_i], c[_j]) for _i, _j in zip(i, j) if (df[c[_i]] == df[c[_j]]).all()]
    return x

# @cmaher's soln
def lambda_set_app(df):
    return list(filter(lambda x: len(x) > 1, list(set([tuple([x for x in df.columns if all(df[x] == df[y])]) for y in df.columns]))))

注意:@John Galt's soln-2 不包括在内,因为输入的大小为 (8000,500) 会与为那个建议的 broadcasting 相冲突。

时间安排 -

In [179]: # Setup inputs with sizes as mentioned in the question
     ...: df = pd.DataFrame(np.random.randint(0,10,(8000,500)))
     ...: df.columns = ['C'+str(i) for i in range(df.shape[1])]
     ...: idx0 = np.random.choice(df.shape[1], df.shape[1]//2,replace=0)
     ...: idx1 = np.random.choice(df.shape[1], df.shape[1]//2,replace=0)
     ...: df.iloc[:,idx0] = df.iloc[:,idx1].values
     ...: 

# @John Galt's soln-1
In [180]: %timeit combinations_app(df)
1 loops, best of 3: 24.6 s per loop

# @Abdou's soln
In [181]: %timeit pandas_groupby_app(df)
1 loops, best of 3: 3.81 s per loop

# @COLDSPEED's soln
In [182]: %timeit triu_app(df)
1 loops, best of 3: 25.5 s per loop

# @cmaher's soln
In [183]: %timeit lambda_set_app(df)
1 loops, best of 3: 27.1 s per loop

# Proposed in this post
In [184]: %timeit group_duplicate_cols(df)
10 loops, best of 3: 188 ms per loop

NumPy 的视图功能的超级提升

利用 NumPy 的视图功能,我们可以将每组元素视为一个 dtype,我们可以获得进一步显着的性能提升,就像这样 -

def view1D(a): # a is array
    a = np.ascontiguousarray(a)
    void_dt = np.dtype((np.void, a.dtype.itemsize * a.shape[1]))
    return a.view(void_dt).ravel()

def group_duplicate_cols_v2(df):
    a = df.values
    sidx = view1D(a.T).argsort()
    b = a[:,sidx]

    m = np.concatenate(([False], (b[:,1:] == b[:,:-1]).all(0), [False] ))
    idx = np.flatnonzero(m[1:] != m[:-1])
    C = df.columns[sidx].tolist()
    return [C[i:j] for i,j in zip(idx[::2],idx[1::2]+1)]

时间安排 -

In [322]: %timeit group_duplicate_cols(df)
10 loops, best of 3: 185 ms per loop

In [323]: %timeit group_duplicate_cols_v2(df)
10 loops, best of 3: 69.3 ms per loop

只是疯狂的加速!

【讨论】:

【参考方案7】:

这是另一种使用纯 Python 的方法:

from operator import itemgetter
from itertools import groupby

def myfunc(df):
    # Convert the dataframe to a list of list including the column name
    zipped = zip(df.columns, df.values.T.tolist())
    # Sort the columns (so they can be grouped)
    zipped_sorted = sorted(zipped, key=itemgetter(1))
    # Placeholder for the result
    res = []
    res_append = res.append
    # Find duplicated columns using itertools.groupby
    for k, grp in groupby(zipped_sorted, itemgetter(1)):
        grp = list(grp)
        if len(grp) > 1:
            res_append(tuple(map(itemgetter(0), grp)))
    return res

我包含了一些内联 cmets 来说明它是如何工作的,但基本上这只是对输入进行排序,使相同的列相邻,然后将它们分组。

我使用 Divakars 计时设置做了一些表面计时,结果如下:

%timeit group_duplicate_cols(df)
391 ms ± 25.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit myfunc(df)
572 ms ± 4.36 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

所以它似乎只比 NumPy 方法慢 2 倍,这实际上是惊人的。

【讨论】:

以上是关于在熊猫数据框中对重复的列 ID 进行分组的主要内容,如果未能解决你的问题,请参考以下文章

根据熊猫数据框中的列标签对数据进行分组

如何在熊猫中对没有聚合功能的列进行分组?

如何在熊猫 DataFrame 中对连续值进行分组

如何同时对熊猫数据框中的列进行排序[重复]

减去熊猫(Python)中按id分组的数据框中的连续行

如何按id python按一列顺序对两列进行分组[重复]