在数据框中计算不同组之间的欧几里得距离。

3

我有各个店铺的周销售数据,格式如下:

pd.DataFrame({'Store':['S1', 'S1', 'S1', 'S2','S2','S2','S3','S3','S3'], 'Week':[1, 2, 3,1,2,3,1,2,3],
                           'Sales' : [20,30,40,21,31,41,22,32,42],'Cust_count' : [2,4,6,3,5,7,4,6,8]})

   Store Week Sales Cust_count
0   S1   1    20    2
1   S1   2    30    4
2   S1   3    40    6
3   S2   1    21    3
4   S2   2    31    5
5   S2   3    41    7
6   S3   1    22    4
7   S3   2    32    6
8   S3   3    42    8

正如您所看到的,数据以存储一周为单位,并且我想计算同一周内每个商店之间的欧几里得距离,然后取计算出的距离的平均值。例如,对于商店S1和S2的计算如下:

    For week 1: sqrt((20-21)^2 + (2-3)^2) = sqrt(2)
    For week 2: sqrt((30-31)^2 + (4-5)^2) = sqrt(2)
    For week 3: sqrt((40-41)^2 + (6-7)^2) = sqrt(2)
    The final value for distance between S1 and S2 = sqrt(2) which is calculated as 
average distance of the 3 weeks i.e. (3 * sqrt(2)) / 3 

最终输出应该如下所示:
   S1    S2      S3
S1 0     1.414   2.818
S2 1.414 0       some val
S3 2.818 some val 0

我对数据框中的列分组使用groupby函数和使用scipy.spatial.distance.cdist计算欧几里德距离有一些想法,但我无法将这些概念联系起来并找到解决方案。
3个回答

3
我们可以使用numpy对数据进行旋转(pivot)后进行计算。
df1  = (df.pivot(index='Store', columns='Week', values=['Sales', 'Cust_count'])
       #  .fillna(0)  # Uncomment if you want to treat missing store-weeks as 0s
       )
arr1 = df1['Sales'].to_numpy()
arr2 = df1['Cust_count'].to_numpy()

data = np.nanmean(np.sqrt(((arr1[None, :] - arr1[:, None])**2 
                         + (arr2[None, :] - arr2[:, None])**2)), 
                  axis=2)

pd.DataFrame(data, index=df1.index, columns = df1.index)

Store        S1        S2        S3
Store                              
S1     0.000000  1.414214  2.828427
S2     1.414214  0.000000  1.414214
S3     2.828427  1.414214  0.000000

2
这很聪明 :-) - BENY
这是一个不错的解决方案。我有几个观察结果,透视函数对我不起作用,显示“数据必须是一维的”。我猜透视表函数在这里应该可以工作。其次,使用透视方法的一个潜在缺点是,因为我有3个月和100个商店的数据,所以在透视之后,至少会创建1200个列,我认为这可能在计算距离时具有计算上的困难。你的想法是什么? - bakas
有一种情况,这个解决方案不能正确工作,即当两个店铺中的周数不同时,例如如果我从S2店铺中删除第3周的数据,则上述解决方案仍然会在S1和S2之间给出相同的值(1.414),尽管在这种情况下,该值应该是1.414(第1周) ,1.414(第2周)和sqrt(40-0)^ 2 +(6-0)^ 2的平均值,用于第3周。 - bakas
@bakas 我不会说它工作不正确。你只是没有指定不存在的周数要进行什么比较,这更加谨慎,并且只在两家店铺都存在数据的周中进行平均值计算(我认为这是合理的:D)。幸运的是修复非常简单;在透视表后面添加.fillna(0)即可按照你所需的方式工作。在描述的情况下,你现在应该获得S1和S2的值为14.42530798568652 - ALollz
1
可能我说话太刻薄了,对此我很抱歉。你是正确的,我应该也提到了这种情况。谢谢 :) - bakas
@bakas 啊,我并没有生气:D。这是一个很好而且有趣的问题。非常乐意帮助:D - ALollz

2

使用permutations的for循环

import itertools
s=list(itertools.permutations(df.Store.unique(), 2))
from scipy import spatial
l=[]
for x in s:
     l.append(np.sqrt(np.mean(np.sum((df[df.Store == x[0]].iloc[:, 2:].values - df[df.Store == x[1]].iloc[:, 2:].values)**2,axis=1),axis=0)))

s=pd.Series(l,index=pd.MultiIndex.from_tuples(s)).unstack()
s
Out[216]: 
          S1        S2        S3
S1       NaN  1.414214  2.828427
S2  1.414214       NaN  1.414214
S3  2.828427  1.414214       NaN

它在样本数据集上运行正常,但当我在完整数据集上运行时,l.append步骤会出现错误,提示“operands could not be broadcast together with shapes (193,2) (97,2)”。这是因为两个商店可能没有相同数量的数据点吗?如果是,我们该如何解决这个问题? - bakas

1
您可以首先按周合并以获取所有商店的组合,然后使用欧氏距离计算列dist,最后使用aggfunc='mean'pivot_table
df.merge(df, on='Week', how='left', suffixes=('','_'))\
  .assign(dist = lambda x: np.sqrt((x.Sales - x.Sales_)**2 + (x.Cust_count - x.Cust_count_)**2))\
  .pivot_table(index='Store', columns='Store_', values='dist', aggfunc='mean')

Store_        S1        S2        S3
Store                               
S1      0.000000  1.414214  2.828427
S2      1.414214  0.000000  1.414214
S3      2.828427  1.414214  0.000000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接