是否有一种优雅的方法只能为矩阵中的每一行保留最高[2~3]值?

时间:2017-11-22 12:36:57

标签: python pandas sorting dataframe

  

更简单的方法已在问题的最后更新。

我有什么

我有一个名为matrixcorr_of_user用户 - 用户相关矩阵,如下所示:

userId       316       320       359       370       910
userId                                                  
316     1.000000  0.202133  0.208618  0.176050  0.174035
320     0.202133  1.000000  0.242837  0.019035  0.031737
359     0.208618  0.242837  1.000000  0.357620  0.175914
370     0.176050  0.019035  0.357620  1.000000  0.317371
910     0.174035  0.031737  0.175914  0.317371  1.000000

我想要什么

  

对于每个用户,我只想保留与他最相似的2个其他用户(排除对角线元素后每行的最高相关值)。像这样:

Out[40]: 
userId          316       320       359       370       910
corr_user                                                  
316             NaN  0.202133  0.208618       NaN       NaN
320        0.202133       NaN  0.242837       NaN       NaN
359             NaN  0.242837       NaN  0.357620       NaN
370             NaN       NaN  0.357620       NaN  0.317371
910             NaN       NaN  0.175914  0.317371       NaN

我知道如何实现它,但我提出的方式太复杂了。 任何人都可以提供更好的主意吗?

我尝试了什么

我首先melt矩阵:

melted_corr = corr_of_user.reset_index().melt(id_vars ="userId",var_name="corr_user")

melted_corr.head()
Out[23]: 
   userId corr_user     value
0     316       316  1.000000
1     320       316  0.202133
2     359       316  0.208618
3     370       316  0.176050
4     910       316  0.174035

filter一行一行:

get_secend_third = lambda x : x.sort_values(ascending =False).iloc[1:3]

filted= melted_corr.set_index("userId").groupby("corr_user")["value"].apply(get_secend_third)

filted
Out[39]: 
corr_user  userId
316        359       0.208618
           320       0.202133
320        359       0.242837
           316       0.202133
359        370       0.357620
           320       0.242837
370        359       0.357620
           910       0.317371
910        370       0.317371
           359       0.175914

最后reshape

filted.reset_index().pivot_table("value","corr_user","userId")
Out[40]: 
userId          316       320       359       370       910
corr_user                                                  
316             NaN  0.202133  0.208618       NaN       NaN
320        0.202133       NaN  0.242837       NaN       NaN
359             NaN  0.242837       NaN  0.357620       NaN
370             NaN       NaN  0.357620       NaN  0.317371
910             NaN       NaN  0.175914  0.317371       NaN

更新

在看到@John Zwinck的答案

之后,我想出了一个更简单的方法

我们假设有一个新的矩阵df,其中包含一些重复的值NaN

userId  316       320       359       370       910
userId                                             
316     1.0  0.500000  0.500000  0.500000       NaN
320     0.5  1.000000  0.242837  0.019035  0.031737
359     0.5  0.242837  1.000000  0.357620  0.175914
370     0.5  0.019035  0.357620  1.000000  0.317371
910     NaN  0.031737  0.175914  0.317371  1.000000

首先我得到每一行的rank

rank = df.rank(1, ascending=False, method="first")

然后我使用df.isin()来获取我想要的面具。

mask = rank.isin(list(range(2,4)))

最后

df.where(mask)

然后我想要我想要的。

userId  316  320       359       370  910
userId                                   
316     NaN  0.5  0.500000       NaN  NaN
320     0.5  NaN  0.242837       NaN  NaN
359     0.5  NaN       NaN  0.357620  NaN
370     0.5  NaN  0.357620       NaN  NaN
910     NaN  NaN  0.175914  0.317371  NaN

3 个答案:

答案 0 :(得分:4)

首先,使用np.argsort()查找哪些位置具有最高值:

sort = np.argsort(df)

这给出了一个DataFrame,其列名无意义,但右边的第二列和第三列包含每行中的所需索引:

        316  320  359  370  910
userId                         
316       4    3    1    2    0
320       3    4    0    2    1
359       4    0    1    3    2
370       1    0    4    2    3
910       1    0    2    3    4

接下来,构造一个布尔掩码,在上述位置设置为true:

mask = np.zeros(df.shape, bool)
rows = np.arange(len(df))
mask[rows, sort.iloc[:,-2]] = True
mask[rows, sort.iloc[:,-3]] = True

现在你有了所需的面具:

array([[False,  True,  True, False, False],
       [ True, False,  True, False, False],
       [False,  True, False,  True, False],
       [False, False,  True, False,  True],
       [False, False,  True,  True, False]], dtype=bool)

最后,df.where(mask)

             316       320       359       370       910
userId                                                  
316          NaN  0.202133  0.208618       NaN       NaN
320     0.202133       NaN  0.242837       NaN       NaN
359          NaN  0.242837       NaN  0.357620       NaN
370          NaN       NaN  0.357620       NaN  0.317371
910          NaN       NaN  0.175914  0.317371       NaN

答案 1 :(得分:1)

这应该有效:

melted_corr['group_rank']=melted_corr.groupby('userId')['value']\
.rank(ascending=False)

然后选择每个用户的前2名:

melted_corr[melted_corr.group_rank<=2]

答案 2 :(得分:1)

这是我的numpy-esque解决方案:

top_k = 3
top_corr = corr_of_user.copy()
top_ndarray = top_corr.values
np.fill_diagonal(top_ndarray, np.NaN)
rows = np.arange(top_corr.shape[0])[:, np.newaxis]
columns = top_ndarray.argsort()[:, :-top_k]
top_ndarray[rows, columns] = np.NaN
top_corr

我们得到了

userId       316       320       359       370       910
userId
316          NaN  0.202133  0.208618       NaN       NaN
320     0.202133       NaN  0.242837       NaN       NaN
359          NaN  0.242837       NaN  0.357620       NaN
370          NaN       NaN  0.357620       NaN  0.317371
910          NaN       NaN  0.175914  0.317371       NaN

如果您不想要副本,而是使用就地解决方案,则可以将top_corr = corr_of_user.copy()替换为top_corr = corr_of_user

这些想法与John Zwinck的想法几乎相同 - 获取必要字段的索引并使用它来索引数组并清除我们不需要的值。我的解决方案的一个小优点是K(我们想要的顶级结果的数量)是一个参数而不是硬编码。当corr_of_user包含所有1时,它也有效。