将熊猫数据框中的重复列ID分组
- 2025-04-10 09:45:00
- admin 原创
- 14
问题描述:
现在有很多类似的问题,但大多数都回答了如何删除重复的列。但是,我想知道如何创建一个元组列表,其中每个元组包含重复列的列名。我假设每列都有一个唯一的名称。只是为了进一步说明我的问题:
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')]
如果你今天感觉很好,那么也可以将同样的问题扩展到行。如何获取每个元组包含重复行的元组列表。
解决方案 1:
以下是 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 的视图功能,我们可以将每组元素视为一个数据类型,从而获得进一步明显的性能提升,如下所示 -
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
只是疯狂的加速!
解决方案 2:
这也应该可以做到:
[tuple(d.index) for _,d in df.T.groupby(list(df.T.columns)) if len(d) > 1]
产量:
# [('A', 'C'), ('B', 'D')]
解决方案 3:
以下是一行代码
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 的解决方案
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')]
解决方案 4:
不使用熊猫,只使用纯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)
解决方案 5:
这是另一种使用纯 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
我添加了一些内联注释来说明它是如何工作的,但基本上这只是对输入进行排序,以便相同的列相邻,然后将它们分组。
我使用 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 倍,这实际上是令人惊奇的。
解决方案 6:
根据@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()]
使用转置(df.T)
解决方案 7:
这里还有一个仅使用理解/内置的选项:
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')]
相关推荐
热门文章
项目管理软件有哪些?
热门标签
曾咪二维码
扫码咨询,免费领取项目管理大礼包!
云禅道AD