在pandas中根据对称矩阵移除序列中的重复项
我刚接触Pandas,遇到一个问题一直找不到简单的解决办法。
假设我有一组数据,是基于一个对称的(距离)矩阵的,那么从这个数据中去掉重复项的最有效方法是什么呢?
from pandas import DataFrame
df = DataFrame([[0, 1, 2],
[1, 0, 3],
[2, 3, 0]],
index=['a', 'b', 'c'],
columns=['a', 'b', 'c'])
ser = df.stack()
ser
a a 0
b 1
c 2
b a 1
b 0
c 3
c a 2
b 3
c 0
我想做的是去掉重复的配对,因为这个矩阵是对称的。最终的结果应该是这样的:
a a 0
b 1
c 2
b b 0
c 3
c c 0
2 个回答
1
我不太确定这个方法效率如何,但这个方法是有效的:
seen = []
for tup in ser.index.tolist():
if tup[::-1] in seen:
continue
seen.append(tup)
ser_reduced = ser[seen]
ser_reduced
Out[9]:
a a 0
b 1
c 2
b b 0
c 3
c c 0
dtype: int64
3
以下代码的运行速度比目前被接受的答案要快:
import numpy as np
def dm_to_series1(df):
df = df.astype(float)
df.values[np.triu_indices_from(df, k=1)] = np.nan
return df.unstack().dropna()
这里把 DataFrame
的类型转换成 float
,这样就可以用 np.nan
来表示空值。实际上,距离矩阵通常已经是浮点数,所以这一步可能不是绝对必要的。我们把上三角部分(不包括对角线)设为 null,然后在把 DataFrame
转换成 Series
后去掉这些条目。
我对目前被接受的解决方案进行了调整,以便比较运行时间。请注意,我把列表改成了集合,这样运行速度更快:
def dm_to_series2(df):
ser = df.stack()
seen = set()
for tup in ser.index.tolist():
if tup[::-1] in seen:
continue
seen.add(tup)
return ser[seen]
在原始示例数据集上测试这两个解决方案:
import pandas as pd
df = pd.DataFrame([[0, 1, 2],
[1, 0, 3],
[2, 3, 0]],
index=['a', 'b', 'c'],
columns=['a', 'b', 'c'])
我的解决方案:
In [4]: %timeit dm_to_series1(df)
1000 loops, best of 3: 538 µs per loop
@Marius 的解决方案:
In [5]: %timeit dm_to_series2(df)
1000 loops, best of 3: 816 µs per loop
我还通过随机生成一个 50x50 的矩阵,使用 scikit-bio 的 skbio.stats.distance.randdm
函数,并把它转换成 DataFrame
来测试更大的距离矩阵:
from skbio.stats.distance import randdm
big_dm = randdm(50)
big_df = pd.DataFrame(big_dm.data, index=big_dm.ids, columns=big_dm.ids)
我的解决方案:
In [7]: %timeit dm_to_series1(big_df)
1000 loops, best of 3: 649 µs per loop
@Marius 的解决方案:
In [8]: %timeit dm_to_series2(big_df)
100 loops, best of 3: 3.61 ms per loop
需要注意的是,我的解决方案可能没有 @Marius 的方案那么节省内存,因为我创建了输入 DataFrame
的一个副本并对其进行了修改。如果可以直接修改输入的 DataFrame
,那么代码可以更新为使用原地操作,这样会更节省内存。
注意:我的解决方案受到 这个 SO 问题 中答案的启发。